Erlang Central

Difference between revisions of "OTP Release Handling Tutorial"

From ErlangCentral Wiki

m (Fixed broken links,)
(9 intermediate revisions by 6 users not shown)
Line 4: Line 4:
  
 
A quick way to get started might be to copy this example system, and modify the configuration files. I will try to explain the purpose of each file and suggest how they may be modified.  
 
A quick way to get started might be to copy this example system, and modify the configuration files. I will try to explain the purpose of each file and suggest how they may be modified.  
 +
 +
You can check out the entire example from this Subversion repository
 +
[http://svn.ulf.wiger.net/release_tutorial http://svn.ulf.wiger.net/release_tutorial].
  
 
===The Example System===
 
===The Example System===
Line 20: Line 23:
 
**releases/
 
**releases/
 
***1.0/
 
***1.0/
****[{{SERVER}}/upload/release_handling_tutorial/releases/1.0/example.rel example.rel]
+
****[http://svn.ulf.wiger.net/release_tutorial/releases/1.0/example.rel example.rel]
****[{{SERVER}}/upload/release_handling_tutorial/releases/1.0/sys.config sys.config]
+
****[http://svn.ulf.wiger.net/release_tutorial/releases/1.0/sys.config sys.config]
 
**lib/
 
**lib/
 
***base-1.0/
 
***base-1.0/
 
****src/
 
****src/
*****[{{SERVER}}/upload/release_handling_tutorial/lib/base-1.0/src/base_app.erl base_app.erl]
+
*****[http://svn.ulf.wiger.net/release_tutorial/lib/base-1.0/src/Emakefile Emakefile]
*****[{{SERVER}}/upload/release_handling_tutorial/lib/base-1.0/src/base_server.erl base_server.erl]
+
*****[http://svn.ulf.wiger.net/release_tutorial/lib/base-1.0/src/base_app.erl base_app.erl]
*****[{{SERVER}}/upload/release_handling_tutorial/lib/base-1.0/src/base_super.erl base_super.erl]
+
*****[http://svn.ulf.wiger.net/release_tutorial/lib/base-1.0/src/base_server.erl base_server.erl]
 +
*****[http://svn.ulf.wiger.net/release_tutorial/lib/base-1.0/src/base_super.erl base_super.erl]
 
****ebin/
 
****ebin/
*****[{{SERVER}}/upload/release_handling_tutorial/lib/base-1.0/ebin/base.app base.app]
+
*****[http://svn.ulf.wiger.net/release_tutorial/lib/base-1.0/ebin/base.app base.app]
 
*****base_app.beam
 
*****base_app.beam
 
*****base_server.beam
 
*****base_server.beam
Line 35: Line 39:
 
***dist-1.0/
 
***dist-1.0/
 
****src/
 
****src/
*****[{{SERVER}}/upload/release_handling_tutorial/lib/dist-1.0/src/dist_app.erl dist_app.erl]
+
*****[http://svn.ulf.wiger.net/release_tutorial/lib/dist-1.0/src/Emakefile Emakefile]
*****[{{SERVER}}/upload/release_handling_tutorial/lib/dist-1.0/src/dist_server.erl|dist_server.erl]
+
*****[http://svn.ulf.wiger.net/release_tutorial/lib/dist-1.0/src/dist_app.erl dist_app.erl]
*****[{{SERVER}}/upload/release_handling_tutorial/lib/dist-1.0/src/dist_super.erl|dist_super.erl]
+
*****[http://svn.ulf.wiger.net/release_tutorial/lib/dist-1.0/src/dist_server.erl dist_server.erl]
 +
*****[http://svn.ulf.wiger.net/release_tutorial/lib/dist-1.0/src/dist_super.erl dist_super.erl]
 
****ebin/
 
****ebin/
*****[{{SERVER}}/upload/release_handling_tutorial/lib/dist-1.0/ebin/dist.app|dist.app]
+
*****[http://svn.ulf.wiger.net/release_tutorial/lib/dist-1.0/ebin/dist.app dist.app]
 
*****dist_app.beam
 
*****dist_app.beam
 
*****dist_server.beam
 
*****dist_server.beam
 
*****dist_super.beam
 
*****dist_super.beam
  
With this file structure in place, you are ready to support in-service upgrade, but that will be described in a future tutorial.  
+
With this file structure in place, you are ready to support in-service upgrade, but that will be described in a future tutorial.
  
 
===Compiling the code.===
 
===Compiling the code.===
...Nothing to it, really. I didn't bother with make scripts and stuff like that. Go into each src/ directory and type:  
+
...Nothing to it, really. I created a simple make file for the Erlang make utility. Go into each src/ directory and type:  
  
<table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0">
+
{{CodeSnippet|Code listing 1.1|<pre>
<tr><td class="infohead" bgcolor="#7a5ada"><p class="caption">
+
erl -make all
            Code listing 1.1</p></td></tr>
+
  </pre>}}
<tr><td bgcolor="#ddddff"><pre>
+
erlc -W -o ../ebin *.erl
+
  </pre></td></tr>
+
</table>
+
  
 
===Building the boot script.===
 
===Building the boot script.===
 
The easiest way to build the boot script is to place yourself in the $DIR/releases/1.0 directory, start an Erlang shell, and type the following:  
 
The easiest way to build the boot script is to place yourself in the $DIR/releases/1.0 directory, start an Erlang shell, and type the following:  
  
<table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0">
+
{{CodeSnippet|Code listing 1.2|<pre>
<tr><td class="infohead" bgcolor="#7a5ada"><p class="caption">
+
            Code listing 1.2</p></td></tr>
+
<tr><td bgcolor="#ddddff"><pre>
+
 
Eshell V5.2  (abort with ^G)
 
Eshell V5.2  (abort with ^G)
 
1&gt;
 
1&gt;
Line 71: Line 69:
 
           started_at: nonode@nohost
 
           started_at: nonode@nohost
  
1&gt; Dir = "/home/etxuwig/work/erlang/release_tutorial".
+
1&gt; Dir = "/home/uwiger/release_tutorial".
"/home/etxuwig/work/erlang/release_tutorial"
+
"/home/uwiger/release_tutorial"
 
2&gt; Path = [Dir ++ "/lib/*/ebin"].
 
2&gt; Path = [Dir ++ "/lib/*/ebin"].
["/home/etxuwig/work/erlang/release_tutorial/lib/*/ebin"]
+
["/home/uwiger/release_tutorial/lib/*/ebin"]
 
3&gt; Var = {"MYAPPS", Dir}.
 
3&gt; Var = {"MYAPPS", Dir}.
{"MYAPPS","/home/etxuwig/work/erlang/release_tutorial"}
+
{"MYAPPS","/home/uwiger/release_tutorial"}
 
4&gt; systools:make_script("example",[{path,Path},{variables,[Var]}]).
 
4&gt; systools:make_script("example",[{path,Path},{variables,[Var]}]).
 
ok
 
ok
  </pre></td></tr>
+
  </pre>}}
</table>
+
  
 
<table class="ncontent" width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td bgcolor="#bbffbb"><p class="note"><b>Note: </b>
 
<table class="ncontent" width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td bgcolor="#bbffbb"><p class="note"><b>Note: </b>
 
  The file  
 
  The file  
  [{{SERVER}}/upload/release_handling_tutorial/releases/1.0/example.rel example.rel] contains hard-coded versions of kernel, stdlib
+
  [http://svn.ulf.wiger.net/release_tutorial/releases/1.0/example.rel example.rel] contains hard-coded versions of kernel, stdlib
 
  and sasl. Depending on your version of OTP, the building of the
 
  and sasl. Depending on your version of OTP, the building of the
 
  boot script may fail. Fortunately, the error message is quite
 
  boot script may fail. Fortunately, the error message is quite
Line 90: Line 87:
 
  </p></td></tr></table>
 
  </p></td></tr></table>
  
Now, you should be able to see an [{{SERVER}}/upload/release_handling_tutorial/releases/1.0/example.rel example.script] file in releases/1.0/. It contains instructions for the Erlang/OTP boot loader. The .script file is converted into an Erlang binary which is stored in example.boot in the same directory.  
+
Now, you should be able to see an [http://svn.ulf.wiger.net/release_tutorial/releases/1.0/example.rel example.script] file in releases/1.0/. It contains instructions for the Erlang/OTP boot loader. The .script file is converted into an Erlang binary which is stored in example.boot in the same directory.
  
Now, you should be able to see an
+
You may be wondering what the variables option is for?
[{{SERVER}}/upload/release_handling_tutorial/releases/1.0/example.script example.script] file
+
  in releases/1.0/. It contains instructions for the Erlang/OTP
+
  boot loader. The .script file is converted into an Erlang binary
+
  which is stored in example.boot in the same directory.
+
  
===Making a tar file.===
+
The boot script compiler will by default build a script that loads all applications from the same lib/ root. If you are testing this code on a workstation, the applications base and dist will reside under one lib/ root and the OTP applications under another. We must point this out to the script compiler, and also define that variable later when we start the system.
 +
 
 +
Note that it is important that the paths are expressed the same way in both the path option and the variables option. The prefix matching is quite naive.
 +
 
 +
===Building a boot script with erlc===
 +
erlc is capable of compiling a .rel file to a boot script. We need to provide the same variables as above. Unfortunately, how to do that is not extremely well documented.
 +
 
 +
The Erlang program that gets called by erlc is erl_compile:compile_cmdline/1, which checks the type of the input file and calls the corresponding compiler - in this case systools:compile_rel/3. This might be good to know if you want to understand how variables are passed.
 +
 
 +
Our call to erlc will look like this:
 +
 
 +
{{CodeSnippet|Code listing 1.3|<pre>
 +
erlc -pz /bome/uwiger/src/release_tut2/lib/base-1.0/ebin \
 +
-pz /home/uwiger/src/release_tut2/lib/dist-1.0/ebin \
 +
+\{variables,\[\{\"MYAPPS\",\"/home/uwiger/src/release_tut2\"\}\]\} \
 +
  example.rel
 +
</pre>}}
 +
 
 +
===Making a tar file===
 
Using systools:make_tar("example", Options) (where Options is the same list of options as for make_script/2, you can pack your release into a tar file, and unpack it on a target system. The -boot_var option makes the code re-locatable. See erl -man systools for more detailed instructions.  
 
Using systools:make_tar("example", Options) (where Options is the same list of options as for make_script/2, you can pack your release into a tar file, and unpack it on a target system. The -boot_var option makes the code re-locatable. See erl -man systools for more detailed instructions.  
  
 
===Running the example.===
 
===Running the example.===
 
There are tricks for starting an embedded system and being able
 
There are tricks for starting an embedded system and being able
  to attach a shell to a node, but that's another tutorial.
+
to attach a shell to a node, but that's another tutorial.
</p>
+
 
<p>
+
I will show how one could easily get something up and running
  I will show how one could easily get something up and running
+
on a Unix workstation. Windows users will have to translate.
  on a Unix workstation. Windows users will have to translate.
+
 
</p>
+
 
*Place yourself in <span class="path">$DIR/releases/1.0</span>(for convenience -- you could of course do this from any directory.)
 
*Place yourself in <span class="path">$DIR/releases/1.0</span>(for convenience -- you could of course do this from any directory.)
 
*Start two shell windows (e.g. xterm)
 
*Start two shell windows (e.g. xterm)
*In one xterm, write:<table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td class="infohead" bgcolor="#7a5ada"><p class="caption">Code listing .3</p></td></tr><tr><td bgcolor="#ddddff"><pre>erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n1</pre></td></tr></table>
+
*In one xterm, write:
*In the other xterm, write:<table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td class="infohead" bgcolor="#7a5ada"><p class="caption">Code listing .4</p></td></tr><tr><td bgcolor="#ddddff"><pre>erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n2</pre></td></tr></table>
+
{{CodeSnippet|Code listing 1.4|<pre>
 +
erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n1
 +
</pre>}}
 +
*In the other xterm, write:
 +
{{CodeSnippet|Code listing 1.5|<pre>
 +
erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n2
 +
</pre>}}
  
  It doesn't really matter if you start both nodes at once, or one
+
It doesn't really matter if you start both nodes at once, or one
  at a time. In the [{{SERVER}}/upload/release_handling_tutorial/releases/1.0/sys.config sys.config]  
+
at a time. In the [http://svn.ulf.wiger.net/release_tutorial/releases/1.0/sys.config sys.config] file, a node synchronization timeout of  
  file, a node synchronization timeout of 10 seconds was
+
10 seconds was specified. After that, the first node will continue  
  specified. After that, the first node will continue alone if the
+
alone if the other node has not yet appeared.
  other node has not yet appeared.
+
  
 
<table class="ncontent" width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td bgcolor="#bbffbb"><p class="note"><b>Note: </b>
 
<table class="ncontent" width="100%" border="0" cellspacing="0" cellpadding="0"><tr><td bgcolor="#bbffbb"><p class="note"><b>Note: </b>
Line 128: Line 143:
 
This is of course an interesting thing to try. If you start n1 first, you may see the following output:  
 
This is of course an interesting thing to try. If you start n1 first, you may see the following output:  
  
<table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0">
+
{{CodeSnippet|Code listing 1.6|<pre>
<tr><td class="infohead" bgcolor="#7a5ada"><p class="caption">
+
[uwiger@debian]: erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n1
            Code listing 1.5</p></td></tr>
+
<tr><td bgcolor="#ddddff"><pre>
+
[etxuwig@cbe1066]: erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n1
+
 
Erlang (BEAM) emulator version 5.2 [hipe] [threads:0]
 
Erlang (BEAM) emulator version 5.2 [hipe] [threads:0]
  
 
Eshell V5.2  (abort with ^G)
 
Eshell V5.2  (abort with ^G)
(n1@cbe1066)1&gt;  
+
(n1@debian)1&gt;  
 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
 
           supervisor: {local,sasl_safe_sup}
 
           supervisor: {local,sasl_safe_sup}
Line 177: Line 189:
 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
 
         application: sasl
 
         application: sasl
           started_at: n1@cbe1066
+
           started_at: n1@debian
 
base_server starting.
 
base_server starting.
  
Line 191: Line 203:
 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
 
         application: base
 
         application: base
           started_at: n1@cbe1066
+
           started_at: n1@debian
 
dist_app:start(normal, _)
 
dist_app:start(normal, _)
 
dist_server starting.
 
dist_server starting.
Line 212: Line 224:
 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
 
         application: dist
 
         application: dist
           started_at: n1@cbe1066
+
           started_at: n1@debian
  
(n1@cbe1066)1&gt;  
+
(n1@debian)1&gt;  
(n1@cbe1066)1&gt; global:whereis_name(dist_server).
+
(n1@debian)1&gt; global:whereis_name(dist_server).
 
&lt;0.58.0&gt;
 
&lt;0.58.0&gt;
(n1@cbe1066)2&gt; dist_server:get_value().
+
(n1@debian)2&gt; dist_server:get_value().
 
undefined
 
undefined
(n1@cbe1066)3&gt; dist_server:set_value(17).
+
(n1@debian)3&gt; dist_server:set_value(17).
 
{ok,undefined}
 
{ok,undefined}
  
</pre></td></tr>
+
</pre>}}
</table>
+
  
 
We can see that the globally registered dist_server is running locally, and we can call the API functions get_value/0 and set_value/1.  
 
We can see that the globally registered dist_server is running locally, and we can call the API functions get_value/0 and set_value/1.  
Line 229: Line 240:
 
  If we now start <span class="code">n2</span>, dist_server should migrate over
 
  If we now start <span class="code">n2</span>, dist_server should migrate over
 
  to that node (since it is so specified in the
 
  to that node (since it is so specified in the
  [{{SERVER}}/upload/release_handling_tutorial/releases/1.0/sys.config sys.config] file.)
+
  [http://svn.ulf.wiger.net/release_tutorial/releases/1.0/sys.config sys.config] file.)
  
<table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0">
+
{{CodeSnippet|Code listing 1.7|<pre>
<tr><td class="infohead" bgcolor="#7a5ada"><p class="caption">
+
[uwiger@debian]: erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n2
            Code listing 1.6</p></td></tr>
+
<tr><td bgcolor="#ddddff"><pre>
+
[etxuwig@cbe1066]: erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n2
+
 
Erlang (BEAM) emulator version 5.2 [hipe] [threads:0]
 
Erlang (BEAM) emulator version 5.2 [hipe] [threads:0]
  
 
Eshell V5.2  (abort with ^G)
 
Eshell V5.2  (abort with ^G)
(n2@cbe1066)1&gt;  
+
(n2@debian)1&gt;  
 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
 
           supervisor: {local,sasl_safe_sup}
 
           supervisor: {local,sasl_safe_sup}
Line 280: Line 288:
 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
 
         application: sasl
 
         application: sasl
           started_at: n2@cbe1066
+
           started_at: n2@debian
 
base_server starting.
 
base_server starting.
  
Line 294: Line 302:
 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
 
         application: base
 
         application: base
           started_at: n2@cbe1066
+
           started_at: n2@debian
dist_app:start({takeover,n1@cbe1066}, _)
+
dist_app:start({takeover,n1@debian}, _)
 
dist_server starting.
 
dist_server starting.
  
Line 309: Line 317:
 
                       {shutdown,10000},
 
                       {shutdown,10000},
 
                       {child_type,worker}]
 
                       {child_type,worker}]
dist_app:start_phase(takeover, {takeover,n1@cbe1066}, _)
+
dist_app:start_phase(takeover, {takeover,n1@debian}, _)
 
dist_app:start_phase(go, _)
 
dist_app:start_phase(go, _)
  
 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
 
         application: dist
 
         application: dist
           started_at: n2@cbe1066
+
           started_at: n2@debian
  
(n2@cbe1066)1&gt;  
+
(n2@debian)1&gt;  
(n2@cbe1066)1&gt; global:whereis_name(dist_server).
+
(n2@debian)1&gt; global:whereis_name(dist_server).
 
&lt;0.59.0&gt;
 
&lt;0.59.0&gt;
(n2@cbe1066)2&gt; dist_server:get_value().
+
(n2@debian)2&gt; dist_server:get_value().
 
17
 
17
</pre></td></tr>
+
</pre>}}
</table>
+
  
 
In the first node, n1, we can see the following output:  
 
In the first node, n1, we can see the following output:  
  
<table class="ntable" width="100%" cellspacing="0" cellpadding="0" border="0">
+
{{CodeSnippet|Code listing 1.7|<pre>
<tr><td class="infohead" bgcolor="#7a5ada"><p class="caption">
+
            Code listing 1.7</p></td></tr>
+
<tr><td bgcolor="#ddddff"><pre>
+
 
=INFO REPORT==== 5-Dec-2002::17:27:15 ===
 
=INFO REPORT==== 5-Dec-2002::17:27:15 ===
 
     application: dist
 
     application: dist
 
     exited: stopped
 
     exited: stopped
 
     type: permanent
 
     type: permanent
</pre></td></tr>
+
</pre>}}
</table>
+
  
 
We can see that dist_server brought the state variable along when migrating to the other node (it did not bring the special function objects along, in order to avoid nasty surprises.)  
 
We can see that dist_server brought the state variable along when migrating to the other node (it did not bring the special function objects along, in order to avoid nasty surprises.)  
  
 
We can now try different combinations of starting and killing the two nodes.
 
We can now try different combinations of starting and killing the two nodes.
 +
 +
==Download xml==
 +
[http://wiki.trapexit.erlang-consulting.com/upload/howto/release_handling_tutorial.xml release_handling_tutorial.xml]
 +
 +
[[Category:HowTo]]

Revision as of 11:47, 30 November 2009

Contents

OTP Release Handling Tutorial.

Introduction

This tutorial attempts to show by example how to build a proper OTP-based system.

A quick way to get started might be to copy this example system, and modify the configuration files. I will try to explain the purpose of each file and suggest how they may be modified.

You can check out the entire example from this Subversion repository http://svn.ulf.wiger.net/release_tutorial.

The Example System

The example system (called "example") runs on two processors (note that the two processors can easily be two Erlang nodes on the same physical machine), and contains two applications:

  • base, which runs on both processors. This program doesn't do much interesting.
  • dist, which runs on only one processor at a time. This program does one interesting thing: smooth takeover of state. This involves start phases, the global name server, and some other exciting concepts.

Basic File Structure

Note:

The code is riddled with comments. If you view it with e.g. Emacs using fontification, it may be easier to read.


With this file structure in place, you are ready to support in-service upgrade, but that will be described in a future tutorial.

Compiling the code.

...Nothing to it, really. I created a simple make file for the Erlang make utility. Go into each src/ directory and type:

Code listing 1.1

erl -make all
	  

Building the boot script.

The easiest way to build the boot script is to place yourself in the $DIR/releases/1.0 directory, start an Erlang shell, and type the following:

Code listing 1.2

Eshell V5.2  (abort with ^G)
1>
<... output snipped>
=PROGRESS REPORT==== 4-Dec-2002::16:55:34 ===
         application: sasl
          started_at: nonode@nohost

1> Dir = "/home/uwiger/release_tutorial".
"/home/uwiger/release_tutorial"
2> Path = [Dir ++ "/lib/*/ebin"].
["/home/uwiger/release_tutorial/lib/*/ebin"]
3> Var = {"MYAPPS", Dir}.
{"MYAPPS","/home/uwiger/release_tutorial"}
4> systools:make_script("example",[{path,Path},{variables,[Var]}]).
ok
	  

Note:

The file example.rel contains hard-coded versions of kernel, stdlib and sasl. Depending on your version of OTP, the building of the boot script may fail. Fortunately, the error message is quite helpful in showing what needs to be changed.

Now, you should be able to see an example.script file in releases/1.0/. It contains instructions for the Erlang/OTP boot loader. The .script file is converted into an Erlang binary which is stored in example.boot in the same directory.

You may be wondering what the variables option is for?

The boot script compiler will by default build a script that loads all applications from the same lib/ root. If you are testing this code on a workstation, the applications base and dist will reside under one lib/ root and the OTP applications under another. We must point this out to the script compiler, and also define that variable later when we start the system.

Note that it is important that the paths are expressed the same way in both the path option and the variables option. The prefix matching is quite naive.

Building a boot script with erlc

erlc is capable of compiling a .rel file to a boot script. We need to provide the same variables as above. Unfortunately, how to do that is not extremely well documented.

The Erlang program that gets called by erlc is erl_compile:compile_cmdline/1, which checks the type of the input file and calls the corresponding compiler - in this case systools:compile_rel/3. This might be good to know if you want to understand how variables are passed.

Our call to erlc will look like this:

Code listing 1.3

erlc -pz /bome/uwiger/src/release_tut2/lib/base-1.0/ebin \
 -pz /home/uwiger/src/release_tut2/lib/dist-1.0/ebin \
 +\{variables,\[\{\"MYAPPS\",\"/home/uwiger/src/release_tut2\"\}\]\} \
  example.rel

Making a tar file

Using systools:make_tar("example", Options) (where Options is the same list of options as for make_script/2, you can pack your release into a tar file, and unpack it on a target system. The -boot_var option makes the code re-locatable. See erl -man systools for more detailed instructions.

Running the example.

There are tricks for starting an embedded system and being able to attach a shell to a node, but that's another tutorial.

I will show how one could easily get something up and running on a Unix workstation. Windows users will have to translate.

  • Place yourself in $DIR/releases/1.0(for convenience -- you could of course do this from any directory.)
  • Start two shell windows (e.g. xterm)
  • In one xterm, write:

Code listing 1.4

erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n1
  • In the other xterm, write:

Code listing 1.5

erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n2

It doesn't really matter if you start both nodes at once, or one at a time. In the sys.config file, a node synchronization timeout of 10 seconds was specified. After that, the first node will continue alone if the other node has not yet appeared.

Note:

The sys.config file contains hard-coded node names. You need to make sure that the node names are correct in your environment for the example to work.

This is of course an interesting thing to try. If you start n1 first, you may see the following output:

Code listing 1.6

[uwiger@debian]: erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n1
Erlang (BEAM) emulator version 5.2 [hipe] [threads:0]

Eshell V5.2  (abort with ^G)
(n1@debian)1> 
=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.45.0>},
                       {name,alarm_handler},
                       {mfa,{alarm_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.46.0>},
                       {name,overload},
                       {mfa,{overload,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.44.0>},
                       {name,sasl_safe_sup},
                       {mfa,{supervisor,
                                start_link,
                                [{local,sasl_safe_sup},sasl,safe]}},
                       {restart_type,permanent},
                       {shutdown,infinity},
                       {child_type,supervisor}]

=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.47.0>},
                       {name,release_handler},
                       {mfa,{release_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
         application: sasl
          started_at: n1@debian
base_server starting.

=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
          supervisor: {local,base_super}
             started: [{pid,<0.53.0>},
                       {name,server},
                       {mfa,{base_server,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,10000},
                       {child_type,worker}]

=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
         application: base
          started_at: n1@debian
dist_app:start(normal, _)
dist_server starting.

=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
          supervisor: {local,dist_super}
             started: [{pid,<0.58.0>},
                       {name,server},
                       {mfa,{dist_server,
                                start_link,
                                [#Fun<dist_super.0.126311943>,
                                 #Fun<dist_super.1.36309860>]}},
                       {restart_type,permanent},
                       {shutdown,10000},
                       {child_type,worker}]
dist_app:start_phase(takeover, _)
dist_app:start_phase(go, _)
handle_call({go, normal},...)

=PROGRESS REPORT==== 5-Dec-2002::16:40:27 ===
         application: dist
          started_at: n1@debian

(n1@debian)1> 
(n1@debian)1> global:whereis_name(dist_server).
<0.58.0>
(n1@debian)2> dist_server:get_value().
undefined
(n1@debian)3> dist_server:set_value(17).
{ok,undefined}

	

We can see that the globally registered dist_server is running locally, and we can call the API functions get_value/0 and set_value/1.

If we now start n2, dist_server should migrate over to that node (since it is so specified in the sys.config file.)

Code listing 1.7

[uwiger@debian]: erl -boot ./example -config ./sys -boot_var MYAPPS $DIR -sname n2
Erlang (BEAM) emulator version 5.2 [hipe] [threads:0]

Eshell V5.2  (abort with ^G)
(n2@debian)1> 
=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.46.0>},
                       {name,alarm_handler},
                       {mfa,{alarm_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
          supervisor: {local,sasl_safe_sup}
             started: [{pid,<0.47.0>},
                       {name,overload},
                       {mfa,{overload,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.45.0>},
                       {name,sasl_safe_sup},
                       {mfa,{supervisor,
                                start_link,
                                [{local,sasl_safe_sup},sasl,safe]}},
                       {restart_type,permanent},
                       {shutdown,infinity},
                       {child_type,supervisor}]

=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
          supervisor: {local,sasl_sup}
             started: [{pid,<0.48.0>},
                       {name,release_handler},
                       {mfa,{release_handler,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,2000},
                       {child_type,worker}]

=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
         application: sasl
          started_at: n2@debian
base_server starting.

=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
          supervisor: {local,base_super}
             started: [{pid,<0.54.0>},
                       {name,server},
                       {mfa,{base_server,start_link,[]}},
                       {restart_type,permanent},
                       {shutdown,10000},
                       {child_type,worker}]

=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
         application: base
          started_at: n2@debian
dist_app:start({takeover,n1@debian}, _)
dist_server starting.

=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
          supervisor: {local,dist_super}
             started: [{pid,<0.59.0>},
                       {name,server},
                       {mfa,{dist_server,
                                start_link,
                                [#Fun<dist_super.0.126311943>,
                                 #Fun<dist_super.1.36309860>]}},
                       {restart_type,permanent},
                       {shutdown,10000},
                       {child_type,worker}]
dist_app:start_phase(takeover, {takeover,n1@debian}, _)
dist_app:start_phase(go, _)

=PROGRESS REPORT==== 5-Dec-2002::17:27:15 ===
         application: dist
          started_at: n2@debian

(n2@debian)1> 
(n2@debian)1> global:whereis_name(dist_server).
<0.59.0>
(n2@debian)2> dist_server:get_value().
17
	

In the first node, n1, we can see the following output:

Code listing 1.7

=INFO REPORT==== 5-Dec-2002::17:27:15 ===
    application: dist
    exited: stopped
    type: permanent
	

We can see that dist_server brought the state variable along when migrating to the other node (it did not bring the special function objects along, in order to avoid nasty surprises.)

We can now try different combinations of starting and killing the two nodes.

Download xml

release_handling_tutorial.xml