Skip to content

Commit

Permalink
notes: add graphviz-based images
Browse files Browse the repository at this point in the history
  • Loading branch information
mtrsk committed Oct 13, 2024
1 parent f0c4b23 commit 7d6ec46
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 47 deletions.
2 changes: 1 addition & 1 deletion layouts/partials/graph.html

Large diffs are not rendered by default.

44 changes: 32 additions & 12 deletions notes/20240930224229-concurrent_erlang.org
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
#+HUGO_TAGS: "Erlang"
#+STARTUP: inlineimages

#+BEGIN_QUOTE
I’m sure you’ve met processes before, but only in the context of operating
systems. In Erlang, processes belong to the programming language and not the
operating system. This means that Erlang processes will have the same logical
behavior on any operating system, so we can write portable concurrent code that
can run on any operating system that supports Erlang.

In Erlang:
+ Creating and destroying processes is very fast.
+ Sending messages between processes is very fast.
+ Processes behave the same way on all operating systems.
+ We can have very large numbers of processes.
+ Processes share no memory and are completely independent.
+ The only way for processes to interact is through message passing.
[cite:@armstrong2013 p.181]
#+END_QUOTE

* Processes

In [[id:de7d0e94-618f-4982-b3e5-8806d88cad5d][Erlang]], creation of a parallel process is achieved by evaluating the ~spawn~
Expand Down Expand Up @@ -56,19 +73,24 @@ Pid = spawn(ModName, FuncName, [Arg1, Arg2, ..., ArgN]).
[[file:../static/img/notes/erlang_send.png]]

The syntax ~Pid ! Msg~ means "send the message ~Msg~ to the process ~Pid~", where ~Msg~
is from any valid [[id:de7d0e94-618f-4982-b3e5-8806d88cad5d][Erlang]] data type. For a ~Pid~ to process a message we need to
pattern match:
is from any valid [[id:de7d0e94-618f-4982-b3e5-8806d88cad5d][Erlang]] data type. Also, ~Pid1 ! Pid2 ! ... ! Msg~ means send the
message Msg to all the processes ~Pid1~, ~Pid2~, and so on. The ~receive ... end~
construct is used for a process to read a message that has been sent:

#+BEGIN_SRC erlang
receive
{From, Message} ->
...
Pattern1 [when Guard1] -> Expression1;
Pattern2 [when Guard2] -> Expression2;
...
end
#+END_SRC

+ Sending a message will never fail
+ A message sent to non-existing processes are throw away
+ Received messages are store in a process' mailbox
+ Sending a message will never fail.
+ A message sent to non-existing processes are throw away.
+ Received messages are store in a process' mailbox.
+ Mailboxes are scanned sequentially.
+ If a message fails to be pattern matched, it's saved in a queue for later
processing and the process waits for the next message.

The erlang shell (~erl~) is itself a process, you can test its message-passing
functionalities by using the ~self~ keyword:
Expand All @@ -80,7 +102,8 @@ end
hello
#+END_SRC

+ Messages can be matched and selectivelly retrieved
+ Messages can be matched and selectivelly retrieved.
+ We can force an order (emulating a priority queue) by using multiple receives:

#+NAME: erlang-seletive
#+BEGIN_SRC dot :file ../static/img/notes/erlang_selective.png :cmdline -Kdot -Tpng :exports results
Expand Down Expand Up @@ -115,16 +138,13 @@ end

this way the message ~{foo, ...}~ is received before the message ~{bar, ...}~.

+ Messages are received when a message matches a clause
+ Mailboxes are scanned sequentially

** Registered Processes
| BIF | Description |
|---------------------+------------------------------------------------------------------------------------|
| ~register(Name, Pid)~ | Associates the name ~Name~, an atom, with the process ~Pid~. |
| ~registered/0~ | Returns a list of names that have been registered using ~register/2~. |
| ~whereis(Name)~ | Returns the pid registered under ~Name~, or ~undefined~ if the name is not registered. |

| ~registered()~ | Return a list of all registered processes in the system. |

+ Sending messages to a non-existing registered process causes the calling
process to terminate with a ~badarg~ error.
Expand Down
198 changes: 170 additions & 28 deletions notes/20241001192402-error_handling_in_erlang.org
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,143 @@
#+HUGO_TAGS: "Erlang"

#+BEGIN_QUOTE
In [[id:de7d0e94-618f-4982-b3e5-8806d88cad5d][Erlang]] we have a large number of processes at our disposal, so the failure
of any individual process is not so important. We usually write only a small
amount of defensive code and instead concentrate on writing corrective code.
We take measures to detect the errors and then correct them after they have
occurred. [cite:@armstrong2013]
In [[id:de7d0e94-618f-4982-b3e5-8806d88cad5d][Erlang]] we do exactly the opposite. We build our applications in two parts: a
part that solves the problem and a part that corrects errors if they have
occurred.

The part that solves the problem is written with as little defensive code as
possible; we assume that all arguments to functions are correct and the programs
will execute without errors.

The part that corrects errors is often generic, so the same error-correcting
code can be used for many different applications. [cite:@armstrong2013 p.201]
#+END_QUOTE

* Definitions
+ Link :: A bi-directional propagation path for exit signals.
+ Exit Signal :: Transmit process termination information.
+ Error trapping :: The ability of an to process exit signals as if they
were messages.
* Error Handling Semantics

** Processes
There are two types of processes: *normal processes* and *system processes*. ~spawn~
(see [[id:63607e63-4428-4578-bf2a-12a49649b49c][Concurrent Erlang]]) creates a normal process. A normal process can become a
system process by evaluating the BIF ~process_flag(trap_exit, true)~.

** Link

To create links, we call the primitive link(Pid), which creates a link between the
calling process and Pid. So, if P1 calls link(P3), a link is created between P1 and
P3.
Linking processes can be thought as an extension of the error handling presented
when dealing with [[id:f0da3356-3797-4ddc-8306-cd333f159754][Sequential Erlang]]. We can, if necessary, catch exceptions
early in the sequential code, that still won't make our application fault
tolerant.

#+begin_quote
For one process to observe another, we must create a ~link~ or ~monitor~ between the
processes. If the linked or monitored processes dies, the observing process is
informed.

Observing processes work transparently across machine boundaries, so a
process running on one machine can monitor the behavior of a process run-
ning on a different machine. This is the basis for programming fault-tolerant
systems. [cite:@armstrong2013 p.200]
#+end_quote

To create links, we call the primitive ~link(Pid)~, which creates a link between the
calling process and ~Pid~. So, if ~Pid1~ calls ~link(Pid2)~, a link is created between ~Pid1~ and
~Pid2~.

#+BEGIN_SRC erlang
PidA = link(PibB)
Pid1 = link(Pid2)
#+END_SRC

+ ~link/1~ creates a bi-directional link between the process calling the BIF (~PidA~) and
the process linked (~PidB~).
+ ~link/1~ creates a bi-directional link between the process calling the BIF (~Pid1~) and
the process linked (~Pid2~).
+ ~spawn_link/3~ will yield the same result as ~spawn/3~ followed by ~link/1~, only that
will do so atomically.
+ ~unlink/1~ removes the link to ~Pid~.

#+NAME: erlang-process-link
#+BEGIN_SRC dot :file ../static/img/notes/erlang_process_link.png :cmdline -Kdot -Tpng :exports results
digraph Link {
node [shape=circle];
rankdir="LR";
sep = 1;

pid1 [label="Pid 1", fontsize="10pt", style=filled, fillcolor=grey];
pid2 [label="Pid 2", fontsize="10pt", style=filled, fillcolor=grey];

pid1 -> pid2 [label="Pid2 = link(Pid1)", dir=both, fontsize="10pt", minlen=1, style=dashed];
}
#+END_SRC

#+RESULTS: erlang-process-link
[[file:../static/img/notes/erlang_process_link.png]]

*** Link Set

The link set of a process ~P~ is the set of processes that are linked to ~P~.

#+NAME: erlang-link-set
#+BEGIN_SRC dot :file ../static/img/notes/erlang_link_set.png :cmdline -Kfdp -Tpng :exports results
digraph LinkSet {
node [fontsize="10pt", shape=circle, style=filled, fillcolor=grey];

pid1 [label="Pid 1", pos="0,0!"];
pid2 [label="Pid 2", pos="0,2!"];
pid3 [label="Pid 3", pos="0,4!"];
pid4 [label="Pid 4", pos="2,0!"];
pid5 [label="Pid 5", pos="2,2!"];
pid6 [label="Pid 6", pos="2,4!"];
pid7 [label="Pid 7", pos="4,0!"];
pid8 [label="Pid 8", pos="4,2!"];
pid9 [label="Pid 9", pos="4,4!"];

pid1 -> pid4 [dir=both, fontsize="10pt", minlen=1, style=dashed];
pid3 -> pid5 [dir=both, fontsize="10pt", minlen=1, style=dashed];
pid4 -> pid5 [dir=both, fontsize="10pt", minlen=1, style=dashed];
pid5 -> pid9 [dir=both, fontsize="10pt", minlen=1, style=dashed];
pid5 -> pid7 [dir=both, fontsize="10pt", minlen=1, style=dashed];
}
#+END_SRC

#+RESULTS: erlang-link-set
[[file:../static/img/notes/erlang_link_set.png]]

*** Groups of Processes That All Die Together

In the previous example, if a single process from the Link Set fails, all the
processes from the Set also fail in cascate. Ultimately, the error signals
propagate to all the linked processes, and the entire group of linked processes
dies.

#+NAME: erlang-link-set-cascade
#+BEGIN_SRC dot :file ../static/img/notes/erlang_link_set_cascade.png :cmdline -Kfdp -Tpng :exports results
digraph LinkSet {
node [fontsize="10pt", shape=circle, style=filled, fillcolor=grey];

pid1 [label="💥", fillcolor="red", pos="0,0!"];
pid2 [label="Pid 2", pos="0,2!"];
pid3 [label="💥", fillcolor="red", pos="0,4!"];
pid4 [label="💥", fillcolor="red", pos="2,0!"];
pid5 [label="💥", fillcolor="red", pos="2,2!"];
pid6 [label="Pid 6", pos="2,4!"];
pid7 [label="💥", fillcolor="red", pos="4,0!"];
pid8 [label="Pid 8", pos="4,2!"];
pid9 [label="💥", fillcolor="red", pos="4,4!"];

pid1 -> pid4 [dir=both, fontsize="10pt", minlen=1, style=dashed];
pid3 -> pid5 [dir=both, fontsize="10pt", minlen=1, style=dashed];
pid4 -> pid5 [dir=both, fontsize="10pt", minlen=1, style=dashed];
pid5 -> pid9 [dir=both, fontsize="10pt", minlen=1, style=dashed];
pid5 -> pid7 [dir=both, fontsize="10pt", minlen=1, style=dashed];
}
#+END_SRC

#+RESULTS: erlang-link-set-cascade
[[file:../static/img/notes/erlang_link_set_cascade.png]]

#+begin_quote
When an Erlang process fails, it sends an explanation to other processes that
are linked to it in the form of a tuple. The tuple contains the atom ~EXIT~, the
~Pid~ of the failed process, and the error as a complex tuple. [cite:@laurent2017 p.100]
#+end_quote

** Exit Signals
+ A process can termination can be *normal* or *abnormal*.
* *Normal*: When there is no more code to execute.
Expand All @@ -56,7 +164,7 @@ P3.
rankdir="LR";
sep = 1;

pid1 [label="Pid 1", fontsize="10pt", style=filled, fillcolor=red];
pid1 [label="💥", fontsize="10pt", style=filled, fillcolor=red];
pid2 [label="Pid 2", fontsize="10pt", style=filled, fillcolor=grey];

pid1 -> pid2 [label="{'Exit', Pid1, Reason}", dir=both, fontsize="10pt", minlen=1, style=dashed];
Expand All @@ -68,6 +176,12 @@ P3.

** Error Trapping

#+begin_quote
If your process is set to trap exits, through a call to ~process_flag(trap_exit,
true)~, these error reports arrive as messages, rather than just killing your
process. [cite:@laurent2017 p.100]
#+end_quote

*** Propagation Semantics

+ Processes can trap ~exit~ signals by calling the BIF ~process_flag(trap_exit, true)~.
Expand All @@ -80,7 +194,7 @@ P3.
rankdir="LR";
sep = 1;

pid1 [label="Pid 1", fontsize="10pt", style=filled, fillcolor=red];
pid1 [label="💥", fontsize="10pt", style=filled, fillcolor=red];
pid2 [label="Pid 2", fontsize="10pt", style=filled, fillcolor=grey; shape=doublecircle];
pid3 [label="Pid 3", fontsize="10pt", style=filled, fillcolor=grey];

Expand All @@ -93,24 +207,52 @@ P3.
#+RESULTS: erlang-error-trapping
[[file:../static/img/notes/erlang_error_trapping.png]]

#+NAME: erlang-error-propagation
#+BEGIN_SRC dot :file ../static/img/notes/erlang_error_propagation.png :cmdline -Kdot -Tpng :exports results
digraph Trapping {
~Pid2~ functions as a firewall, stopping errors from propagating to other processes in the system.

** Monitors

Monitors are similar to links but with several significant differences.

+ Monitors are unidirectional.

A process ~Pid1~ can create a monitor for ~Pid2~ by calling the BIF
~erlang:monitor(process, Pid2)~. The function returns a reference ~Ref~.

#+NAME: erlang-monitor
#+BEGIN_SRC dot :file ../static/img/notes/erlang_monitor.png :cmdline -Kdot -Tpng :exports results
digraph Monitor {
node [shape=circle];
rankdir="LR";
sep = 1;

pid1 [label="Pid 1", fontsize="10pt", style=filled, fillcolor=red];
pid2 [label="Pid 2", fontsize="10pt", style=filled, fillcolor=grey; shape=doublecircle];
pid3 [label="Pid 3", fontsize="10pt", style=filled, fillcolor=grey];
pid1 [label="Pid 1", fontsize="10pt", style=filled, fillcolor=grey];
pid2 [label="Pid 2", fontsize="10pt", style=filled, fillcolor=grey, shape=square];

pid1 -> pid2 [fontsize="10pt", minlen=2, style=dashed];
}
#+END_SRC

#+RESULTS: erlang-monitor
[[file:../static/img/notes/erlang_monitor.png]]

pid1 -> pid2 [label="{'Exit', Pid1, Reason}", dir=both, fontsize="10pt", minlen=1, style=dashed];
pid2 -> pid3 [dir=both, fontsize="10pt", minlen=2, style=dashed];
+ When a monitored process dies, a "down" message (not an ~exit~ signal) is sent
to the monitoring process.

#+NAME: erlang-monitor-error
#+BEGIN_SRC dot :file ../static/img/notes/erlang_monitor_error.png :cmdline -Kdot -Tpng :exports results
digraph Monitor {
node [shape=circle];
rankdir="LR";
sep = 1;

pid1 [label="💥", fontsize="10pt", style=filled, fillcolor=red];
pid2 [label="Pid 2", fontsize="10pt", style=filled, fillcolor=grey, shape=square];

pid1 -> pid2 [label="{'DOWN', Ref, process, Pid1, Reason}", fontsize="10pt", minlen=1, style=dashed];
}
#+END_SRC

#+RESULTS: erlang-error-propagation
[[file:../static/img/notes/erlang_error_propagation.png]]
#+RESULTS: erlang-monitor-error
[[file:../static/img/notes/erlang_monitor_error.png]]

#+print_bibliography:
46 changes: 45 additions & 1 deletion notes/20241004123538-erlang_shell.org
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,16 @@ terminates the Erlang session. [cite:@armstrong2013 p.14]
> f().
#+END_SRC

* Modifying the Development Environment
** Getting Help

#+begin_quote
The Erlang shell has a number of built-in commands. You can see them all
with the shell command ~help()~. [cite:@armstrong2013 p.173].
#+end_quote

* Compiling and running Erlang programs

** Modifying the Environment

You can find the value of the current load path by starting an Erlang shell
and giving the command ~code:get_path()~. The two most common functions that we
Expand All @@ -82,4 +91,39 @@ Alternatively, you can start Erlang with a command like this:
$ erl -pa Dir1 -pa Dir2 ... -pz DirK1 -pz DirK2
#+end_src

** Scripting
Often we want to be able to execute an arbitrary Erlang function from the OS command
line. The ~-eval~ argument is very handy for quick scripting.

#+begin_src shell
erl \
-eval 'io:format("Memory: ~p~n", [erlang:memory(total)]).' \
-noshell -s init stop
#+end_src

*** Run As an Escript

#+begin_src erlang
#!/usr/bin/env escript

main([A]) ->
I = list_to_integer(A),
F = fac(I),
io:format("factorial ~w = ~w~n", [I, F]).

fac(0) ->
1;
fac(N) ->
N * fac(N - 1).
#+end_src

* Debugging
** Reading Crash Dumps
If Erlang crashes, it leaves behind a file called ~erl_crash.dump~. To start the
analyzer, give the following command:

#+begin_src shell
crashdump_viewer:start()
#+end_src

#+print_bibliography:
2 changes: 1 addition & 1 deletion static/graph.json

Large diffs are not rendered by default.

Loading

0 comments on commit 7d6ec46

Please sign in to comment.