Message Passing
CS511
1/61
Message Passing
Exceptions
Links and Monitors
2/61
Interaction Models
Previously
Shared memory (low-level, non-structured)
Semaphores (low-level, non-structured)
Monitors (popular, structured, encapsulate synchronization)
So what’s the problem with monitors?
Highly centralized (un/blocking processes, maintaining queues
of blocked processes, encapsulating data)
For modern, distributed architectures, need for less centralized
solution
Turn to interaction through communication rather than sharing
3/61
The Message Passing Model
No shared memory
A process sends a message
Another process receives the message
Operations: receive(Var);
send(PID,msg);
receive blocks until a message is available in the mailbox
send(PID,msg) is non-blocking; it sends message msg to process
PID
This model is the asynchronous communication model and is the one used in Erlang
4/61
Nodes and Processes in Erlang1
A distributed Erlang system consists of a number of Erlang runtime systems communicating with each other (instances of the VM)
Each such runtime system is called a node
1Source: https://blog.stenmans.org/theBeamBook/
5/61
Distributed Nodes in Erlang
A distributed Erlang system consists of a number of Erlang runtime systems communicating with each other (instances of the VM)
6/61
Nodes and Processes in Erlang
Each such runtime system is called a node node name is an atom name@host
name is the name given by the user
host is the full host name if long names are used, or the first
part of the host name if short names are used
The name of a node may be consulted using node()
1 1> node().
2 nonode@nohost
7/61
Processes and Communication in Erlang
A process in a node has a process id (pid)
1 1> self().
2 <0.78.0 >
its own memory (a mailbox, a heap and a stack); and
a process control block (PCB) with information about the
process.
Message passing between processes at different nodes, as well as links and monitors, are transparent when pids are used
Registered names, however, are local to each node.
Format of a PID:
node id where process lives; 0 if node is local
process index itelf (index into process table)
serial which increases every time MAXPROCS has been
reached.
8/61
A Simple Echo Server
1 2 3 4 5 6 7
Process echo will receive a message and then send it back to the sender
After that it will continue to wait for a new message
It may be stopped by sending it the stop message
echo() -> receive
{From, Msg} -> From ! {Msg},
echo (); stop -> true
end.
Processes are created using spawn/1 and spawn/3
9/61
A Simple Echo Server (cont.)
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19 20 21 22
-module(echo). -export([start/0]).
echo() -> receive
{From, Msg} -> From ! {Msg},
echo (); stop -> true
end.
start() ->
Pid = spawn(fun echo/0), % Returns pid of a new process
% started by the application of echo/0 to []
Token = “Hello Server!”, % Sending tokens to the server Pid ! {self(), Token},
io:format(“Sent ~s~n”,[Token]),
receive
{Msg} ->
io:format(“Received ~s~n”, [Msg])
end,
Pid ! stop. % Stop server
10/61
A Simple Echo Server
1 2 3 4
1 2 3 4 5 6
1> echo:start( ).
Sent Hello Server! Received Hello Server! stop
If we export echo/0 we can spawn from the interpreter:
59> X=spawn(fun echo:echo/0). <0.198.0 >
60> X!{self(),”hello”}. {<0.60.0>,”hello”}
61> X. <0.198.0 >
Note: the value of a send is the value of the message
11/61
Reacting to Multiple Messages
Erlang “listens” for messages from different senders
In which order will they be processed?
Can we force an order?
A receive statement tries to find a match as early in the
mailbox as it can
1 receive
2 msg3 -> 42 3 end
12/61
msg1
msg2
msg3
msg4
mailbox
msg1
msg2
msg4
mailbox
Reacting to Multiple Messages
1 receive
2 msg4 -> 42 3 end
msg1
msg2
msg4
mailbox
msg1
msg2
mailbox
13/61
Reacting to Multiple Messages
Waiting for multiple messages
1 receive
2 msg4 -> 42; 3 _ ->41 4 end
msg1
msg2
msg4
mailbox
msg2
msg4
mailbox
The oldest message is tried against every pattern of the receive until one of them matches
14/61
Sources of Multiple Messages
Multiple messages can come from different processes
Process B
Process A
Process C
How do we know who sent a message? Distinguish the source by Pids
15/61
Sources of Multiple Messages
1 2 3 4 5 6 7 8 9
10 11 12 13 14
-module(echo2). -export([start/0]).
echo() -> receive
{From, Msg} -> timer:sleep(rand:uniform(100)), From ! {Msg},
echo ();
%
stop -> true
end.
continued on next
slide …
timer:sleep(N) sleeps a process for N milliseconds
rand:uniform(N) produces a random integer between 1 and N
16/61
Sources of Multiple Messages
1 start() ->
2 PidB = spawn(fun echo/0),
3 PidC = spawn(fun echo/0),
4
5 % sending tokens
6 Token = 42,
7 PidB ! {self(), Token},
8 io:format(“Sent~w~n”,[Token]),
9 Token2 = 41,
10 PidC ! {self(), Token2},
11 io:format(“Sent~w~n”,[Token2]),
12
13 % receive message
14 receive
15 {Msg} ->
16 io:format(“Received ~w~n”, [Msg])
17 end,
18
19 % stop echo-servers
20 PidB ! stop ,
21 PidC ! stop.
17/61
Sources of Multiple Messages
1 2 3 4 5 6 7 8 9
10 11 12 13 14
How do we know who sent a message?
Distinguish the source by Pids -module(echo2).
-export([start/0]).
echo() -> receive
%
stop -> true
end.
continued on next
slide …
{From, Msg} -> timer:sleep(rand:uniform(100)), From ! {self(), Msg},
echo ();
18/61
Sources of Multiple Messages
1 start() ->
2 PidB = spawn(fun echo/0),
3 PidC = spawn(fun echo/0),
4
5 % sending tokens
6 Token = 42,
7 PidB ! {self(), Token},
8 io:format(“Sent~w~n”,[Token]),
9 Token2 = 41,
10 PidC ! {self(), Token2},
11 io:format(“Sent~w~n”,[Token2]),
12
13 % receive messages
14 receive
15 {PidB, Msg} ->
16 io:format(“Received from B: ~w~n”, [Msg]) ;
17 {PidC, Msg} ->
18 io:format(“Received from C: ~w~n”, [Msg])
19 end,
20
21 % stop echo-servers
22 PidB ! stop ,
23 PidC ! stop.
19/61
Sources of Multiple Messages
1 11> echo2:start(). 2 Sent42
3 Sent41
4 Received from B: 42 5 stop
6 12> echo2:start(). 7 Sent42
8 Sent41
9 Received from B: 42
10 stop
11 13> echo2:start(). 12 Sent42
13 Sent41
14 Received from C: 41 15 stop
16 14> echo2:start(). 17 Sent42
18 Sent41
19 Received from B: 42 20 stop
20/61
Sources of Multiple Messages
Multiple messages can come from the same processes
Send several messages of the same shape and continue computing
When receiving the responses, how can the code match them to the appropriate request?
BIF make_ref provides globally unique reference objects (references for short) different from every other object in the Erlang system including remote nodes
References can be used to uniquely identify messages
21/61
Sources of Multiple Messages
1 2 3 4 5 6 7 8 9
10 11 12 13
-module(echo3). -export([start/0]).
echo() -> receive
%
stop -> true
end.
continues in next
slide …
{From, Ref, Msg} ->
From ! {self(), Ref, Msg}, echo ();
22/61
Sources of Multiple Messages
1 start() ->
2 PidB = spawn(fun echo/0),
3 % sending tokens
4 Token = 42,
5 Ref = make_ref(),
6 PidB ! {self(), Ref, Token},
7 io:format(“Sent~w~n”,[Token]),
8 Token2 = 41,
9 Ref2 = make_ref(),
10 PidB ! {self(), Ref2, Token2},
11 io:format(“Sent~w~n”,[Token2]),
12 % receive messages
13 receive
14 {PidB , Ref2 , Msg} ->
15 io:format(“Received 41? ~w~n”, [Msg]) ;
16 {PidB, Ref, Msg} ->
17 io:format(“Received 42? ~w~n”, [Msg])
18
19 end, 20
21 % stop echo-servers
22 PidB ! stop.
23/61
Selective Receive
Clauses can have guards
Guards must be composed of terminating functions (BIFs)
1 receive
2 {Pid, Ref, N} when N>0 -> …
24/61
Timeouts
1 2 3 4 5 6
f(Pid) -> receive
{Pid, Msg} -> Msg after 3000 ->
timeout
end.
The after part will be triggered if 3000 milliseconds have passed without receiving a message that matches the pattern.
Other uses
sleep(T) -> receive
after T -> ok
end.
flush() -> receive
1 2 3 4 5 6 7 8 9
10 11 12
flush ()
ok end.
_ -> after 0 ->
25/61
Exercise
1 2 3 4 5 6 7
Implement a semaphore
Use the when clause
Template that you can start from:
-module(semaphore). -compile(export_all).
make_semaphore(Permits) -> spawn(?MODULE,semaphore,[Permits]).
% complete
?MODULE: macro that refers to the name of the current module
26/61
A Semaphore
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19 20
-module(semaphore). -compile(export_all).
make_semaphore(Permits) -> spawn(?MODULE,semaphore,[Permits]).
semaphore (0) -> receive
{From,Ref,release} -> semaphore (1)
end;
semaphore(P) when P>0 ->
receive {From,Ref,release} ->
From!{self(),Ref,ok},
semaphore(P+1); {From,Ref,acquire} ->
From!{self(),Ref,ok}, semaphore(P-1)
end.
semaphore could be specified as a FSM
27/61
Semaphore – Print “a” before “b”
1 start() ->
2 S = make_semaphore(0),
3 spawn(?MODULE,p1,[S]),
4 spawn(?MODULE,p2,[S]).
5
6 release(S) -> % could be included in semaphore module
7 R = make_ref(),
8 S!{self(),R,release},
9 receive
10 {S,R,ok} ->
11 done
12 end.
13
14 p1(S) ->
15 io:format(“a”),
16 release(S).
17
18 p2(S) -> % acquire is inlined
19 R = make_ref(),
20 S!{self(),R,acquire},
21 receive
22 {S,R,ok} ->
23 io:format(“b”)
24 end.
28/61
Message Passing
Exceptions
Links and Monitors
29/61
Three Kinds of Exceptions
Errors
Ends the execution in the current process and includes a stack
trace of the last functions
Errors are the means for a function to stop its execution when
you can’t expect the calling code to handle what just happened
Throws
Used for cases that the programmer can be expected to handle
(try…catch).
Exits
Same as errors except used to signal abnormal termination
between processes.
More lightweight than errors in that stack trace not included
Note: try…catch actually can catch them all
30/61
Errors – Example
1 2 3 4 5 6 7 8 9
10 11
1> erlang:error(badarith).
** exception error: bad argument in an arithmetic expression 2> erlang:error(custom_error).
** exception error: custom_error
3> catch(1+a).
{’EXIT’,{badarith ,[{erlang ,’+’,[1,a],[]},
{erl_eval,do_apply,6,[{file,”erl_eval.erl”},{line,681}]}, {erl_eval,expr,5,[{file,”erl_eval.erl”},{line,434}]}, {shell,exprs,7,[{file,”shell.erl”},{line,686}]}, {shell,eval_exprs,7,[{file,”shell.erl”},{line,642}]}, {shell,eval_loop,3,[{file,”shell.erl”},{line,627}]}]}
31/61
Message Passing
Exceptions
Links and Monitors
32/61
Links
Pid1 can be linked to Pid2 by calling link(Pid2) Creates a two-way link
Terminating processes emit exit signals to all linked processes, which can terminate as well or handle the exit in some way.
This feature can be used to build hierarchical program structures where some processes are supervising other processes, for example, restarting them if they terminate abnormally.
Note: Some comments on monitors are present at the end of these set of slides
33/61
Example
1 2 3 4 5 6
1 2 3 4 5 6 7 8 9
10 11 12 13
-module(linkmon). -compile(export_all).
myproc() -> timer:sleep(2000), exit(reason).
In the shell:
> c(linkmon). {ok,linkmon} > self(). <0.79.0 >
> spawn(fun linkmon:myproc/0). <0.75.0 >
> self().
<0.79.0 >
> link(spawn(fun linkmon:myproc/0)). true
** exception error: reason
> self().
<0.83.0>
34/61
Another Example
1 2 3 4 5 6 7 8 9
10 11 12 13
1 2 3 4 5
chain(0) -> receive
_ -> ok after 2000 ->
exit(“chain dies here”) end;
chain(N) ->
Pid = spawn(fun() -> chain(N-1) end), link(Pid),
receive
_ -> ok end.
In the shell:
1> c(linkmon).
{ok,linkmon}
2> link(spawn(linkmon ,
true
** exception error: “chain dies here”
chain ,
[3])).
35/61
Another Example (cont.)
[shell] == [3] ==
[shell] == [3] ==
[shell] == [3] ==
[shell] == [3] ==
[shell] == *dead*
*dead, error message shown* [shell] <-- restarted
[2] == [1] == [0] [2] == [1] == *dead* [2] == *dead*
*dead*
After the process running linkmon:chain(0) dies, the error is propagated down the chain of links until the shell process itself dies because of it.
The crash could have happened in any of the linked processes
because links are bidirectional, you only need one of them to die for the others to follow suit.
36/61
On Number of Links and Linking
Links cannot be stacked.
Calling link/1 multiple times for the same two processes, will
still create only one link between them
A single call to unlink/1 will be enough to tear it down.
link(spawn(Function)) or link(spawn(M,F,A)) happens in more than one step. In some cases, it is possible for a process to die before the link has been set up and then provoke unexpected behavior.
spawn_link/1-3 spawns and links as an atomic operation
37/61
Trapping Exit Signals
In order to be reliable, an application needs to be able to both kill and restart a process quickly.
Links convenient for the killing part but restarting is missing.
When a linked process terminates, it terminates with an exit reason that is sent through a special message known as an exit signal
Eg. exit signal with exit reason "chain dies here" exit("chain dies here")
38/61
Trapping Exit Signals
The default behaviour when a process receives an exit signal with an exit reason other than normal, is to terminate and in turn emit exit signals with the same exit reason to its linked processes.
System processes: normal processes, except they can convert exit signals to regular messages.
Done by calling process_flag(trap_exit, true) in a running process.
Allows a process to react to exit signals
39/61
Chain Example Revisited
Chain example with a system process at the beginning
1 1> process_flag(trap_exit , true).
2 true
3 2> spawn_link(fun() -> linkmon:chain(3) end).
4 <0.49.0 >
5 3> receive X -> X end.
6 {’EXIT’,<0.49.0>,”chain dies here”}
Description of behavior:
[shell] == [3] == [2] == [1] == [0] [shell] == [3] == [2] == [1] == *dead* [shell] == [3] == [2] == *dead* [shell] == [3] == *dead*
[shell] <-- {’EXIT,Pid,"chain dies here"} -- *dead* [shell] <-- still alive!
40/61
Kill Reason
Acts as a special signal that can’t be trapped.
Ensures any process you terminate with it will be dead.
A last resort, when everything else has failed.
As the kill reason can never be trapped, it needs to be changed to killed when other processes receive the message.
Otherwise, every other process linked to it would in turn die for the same kill reason and would in turn kill its neighbors, and so on.
This explains why exit(kill) looks like killed when received from another linked process.
1 > spawn_link(fun() -> exit(kill) end).
2 ** exception exit: killed
41/61
MSC for Critic Example
42/61
Judge Critic
{PID_J,{Band,Album}}
{PID_C,Answer}
Restarting Processes
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19 20 21 22 23
start_critic() -> spawn(?MODULE, critic, []).
judge(Pid, Band, Album) ->
Pid ! {self(), {Band, Album}},
receive
{Pid, Criticism} -> Criticism after 2000 ->
timeout end.
critic() -> receive
{From, {“Rage Against the Turing Machine”, “Unit Testify”}} -> From ! {self(), “They are great!”};
{From, {“System of a Downtime”, “Memoize”}} ->
From ! {self(), “They’re not Johnny Crash but they’re good.”
{From, {“Johnny Crash”, “The Token Ring of Fire”}} -> From ! {self(), “Simply incredible.”};
{From, {_Band, _Album}} ->
From ! {self(), “They are terrible!”}
end, critic().
43/61
Restarting Processes
1 2 3 4 5 6
1 2 3 4
1> c(linkmon).
{ok,linkmon}
2> Critic = linkmon:start_critic().
<0.47.0 >
3> linkmon:judge(Critic, “Genesis”, “The Lambda Lies Down on Broad “They are terrible!”
We now kill the Critic process
4> exit(Critic, solar_storm).
true
5> linkmon:judge(Critic, “Genesis”, “A trick of the Tail Recursion timeout
We need a “supervisor” process to keep critics alive
44/61
MSC for Critic Example
45/61
Judge Critic Restarter
{PID_J,{Band,Album}}
{PID_C,Answer}
{‘EXIT’,PID_C,Abnormal}
spawn_new_critic
Restarting Processes
1 2 3 4 5 6 7 8 9
10 11 12 13 14
1 2 3 4 5
start_critic2() -> spawn(?MODULE, restarter, []).
restarter () ->
process_flag(trap_exit , true),
Pid = spawn_link(?MODULE, critic, []), receive
{’EXIT’, Pid, normal} -> % not a crash ok;
{’EXIT’, Pid, shutdown} -> % manual termination, not a crash ok;
{’EXIT’, Pid, _} -> restarter()
end.
Problem: Pid of the critic is part of internal state, it is not known
1> c(linkmon).
{ok,linkmon}
2> linkmon:start_critic2().
<0.48.0 >
3> linkmon:judge(?????, “Genesis”, “The Lambda Lies Down on Broadw
46/61
Restarting Processes
1 2 3 4 5 6 7 8 9
10 11 12
We can name a process, using an atom, rather than use its pid via erlang:register/2
If a process dies, it will automatically lose its name or you can also use unregister/1
You can get a list of all registered processes with registered/0 or a more detailed one with the shell command regs().
restarter () ->
process_flag(trap_exit , true),
Pid = spawn_link(?MODULE, critic, []), register(critic , Pid),
receive
{’EXIT’, Pid, normal} -> % not a crash ok;
{’EXIT’, Pid, shutdown} -> % manual termination, not a crash ok;
{’EXIT’, Pid, _} -> restarter()
end.
What about the judge?
47/61
Restarting a Process
1 2 3 4 5 6 7 8
judge2(Band, Album) ->
receive
{Pid, Criticism} -> Criticism
after 2000 -> timeout
end.
critic ! {self(), {Band, Album}}, Pid = whereis(critic),
48/61
Restarting a Process
1 1> linkmon:start_critic2().
2 <0.58.0 >
3 2> whereis(critic).
4 <0.59.0 >
5 3> linkmon:judge2(“Genesis”, “A trick of the Tail Recursion”).
6 “They are terrible!”
7 4> exit(whereis(critic),solar_storm).
8 true
9 5> linkmon:judge2(“Genesis”, “A trick of the Tail Recursion”).
10 “They are terrible!”
11 6> whereis(critic).
12 <0.63.0>
49/61
Race Conditions due to Shared State
critic is stored in a shared registry
There are processes that read it such as judge2 And processes that write to it such as restarter Race conditions are therefore possible
50/61
Race Conditions due to Shared State – Example 1
1. critic ! Message
5. whereis fails 7. code crashes
2. critic receives 3. critic replies 4. critic dies
6. critic is restarted
1 judge2(Band, Album) ->
2 critic ! {self(), {Band, Album}},
3 %% critic dies at this point
4 %% register still not updated
5 Pid = whereis(critic), %% fails (returns undefined)
6 receive
7 {Pid, Criticism} -> Criticism %% undefined!=Pid
8 after 2000 ->
9 timeout
10 end.
51/61
Race Conditions due to Shared State – Example 2
1. critic ! Message
2. critic receives
3. critic replies
4. critic dies
5. critic is restarted
6. whereis picks up wrong pid 7. message never matches
1 judge2(Band, Album) ->
2 critic ! {self(), {Band, Album}},
3 %% critic dies at this point
4 %% register updated with new Pid
5 Pid = whereis(critic), %% successful (but different Pid)
6 receive
7 {Pid, Criticism} -> Criticism %% no match
8 after 2000 ->
9 timeout
10 end.
Both may be solved by replacing the use of whereis (and Pid matching) to that of reference matching
52/61
Adding References to Messages
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19 20 21
judge2(Band, Album) ->
Ref = make_ref(),
critic ! {self(), Ref, {Band, Album}}, receive
{Ref, Criticism} -> Criticism after 2000 ->
timeout end.
critic2() -> receive
{From, Ref, {“Rage Against the Turing Machine”, “Unit Testify” From ! {Ref, “They are great!”};
{From, Ref, {“System of a Downtime”, “Memoize”}} ->
From ! {Ref, “They’re not Johnny Crash but they’re good.”};
{From, Ref, {“Johnny Crash”, “The Token Ring of Fire”}} -> From ! {Ref, “Simply incredible.”};
{From, Ref, {_Band, _Album}} ->
From ! {Ref, “They are terrible!”}
end, critic2 ().
53/61
Appendix: More on Exceptions
Appendix: Monitors
54/61
Revisiting Exceptions – How Processes Trap Them
spawn_link(fun() ->ok end)
Untrapped Result: Nothing
Trapped Result: {’EXIT’, <0.61.0>, normal}
The process exited normally, without a problem.
spawn_link(fun() ->exit(reason) end)
Untrapped Result: ** exception exit: reason
Trapped Result: {’EXIT’, <0.55.0>, reason}
The process has terminated for a custom reason.
spawn_link(fun() ->exit(normal) end) Untrapped Result: Nothing
Trapped Result: {’EXIT’, <0.58.0>, normal} Emulates process terminating normally.
55/61
Revisiting Exceptions
spawn_link(fun() ->1/0 end)
Untrapped Result:
Error in process <0.44.0> with exit value: {badarith, [{erlang, ’/’,
Trapped Result:
{’EXIT’, <0.52.0>, {badarith, [{erlang, ’/’, [1,0]}]} spawn_link(fun() ->erlang:error(reason) end)
Untrapped Result:
Error in process <0.47.0> with exit value: {reason, [{erlang, apply,
Trapped Result:
{’EXIT’, <0.74.0>, {reason, [{erlang, apply, 2}]]}}
Similar to 1/0.
spawn_link(fun() ->throw(rocks) end)
Untrapped Result:
Error in process <0.51.0> with exit value: {{nocatch, rocks}, [{erlan
Trapped Result:
{’EXIT’, <0.79.0>, {{nocatch, rocks}, [{erlang, apply, 2}]}}
Because the throw is never caught by a try … catch, it bubbles up into an error, which in turn bubbles up into an EXIT. Without trapping exit, the process fails.
56/61
Revisiting Exceptions – the exit/2 case
Allows a process to kill another one from a distance, safely
exit(self(), normal)
Untrapped Result: ** exception exit: normal
Trapped Result: {’EXIT’, <0.31.0>, normal}
When not trapping exits, exit(self(), normal) acts the same
as exit(normal).
exit(spawn_link(fun() ->timer:sleep(50000) end), normal) Untrapped Result: nothing
Trapped Result: nothing
exit(spawn_link(fun() ->timer:sleep(50000) end), reason)
Untrapped Result: ** exception exit: reason Trapped Result: {’EXIT’, <0.52.0>, reason}
57/61
Revisiting Exceptions – the exit/2 case
exit(spawn_link(fun() ->timer:sleep(50000) end), kill) Untrapped Result: ** exception exit: killed
Trapped Result: {’EXIT’, <0.58.0>, killed}
exit(self(), kill)
Untrapped Result: ** exception exit: killed Trapped Result: ** exception exit: killed
spawn_link(fun() ->exit(kill) end)
Untrapped Result: ** exception exit: killed Trapped Result: {’EXIT’, <0.67.0>, kill}
58/61
Monitors
Special type of link with two differences they are unidirectional,
can monitor via a registered name, and they can be stacked.
Allows a process to, unobtrusively, monitor another one
Useful for when you have multiple libraries that you call and they all need to know whether a process is alive or not
You can stack links and remove them individually
59/61
Example
erlang:monitor/2 sets up a monitor, where the first argument is the
atom process and the second one is the pid
1 1> erlang:monitor(process, spawn(fun() -> timer:sleep(500) end)). 2 #Ref<0.0.0.77>
3 2> flush().
4 Shell got {’DOWN’,#Ref<0.0.0.77>,process,<0.63.0>,normal}
5 ok
When monitored process goes down, send message to
monitor: {’DOWN’, MonitorReference, process, Pid, Reason}.
The reference allows you to demonitor the process.
Monitors are stackable, so it’s possible to take more than one
down.
References allow you to track each of them in a unique
manner.
60/61
Example
Atomic function to spawn process while monitoring it:
1 3> {Pid, Ref} = spawn_monitor(fun() -> receive _ -> exit(boom) end 2 {<0.73.0>,#Ref<0.0.0.100>}
3 4> erlang:demonitor(Ref).
4 true
5 5> Pid ! die. 6 die
7 6> flush().
8 ok
We demonitored the other process before it crashed hence no trace of it dying.
61/61