diff options
Diffstat (limited to 'system/doc/design_principles/appup_cookbook.xml')
-rw-r--r-- | system/doc/design_principles/appup_cookbook.xml | 627 |
1 files changed, 627 insertions, 0 deletions
diff --git a/system/doc/design_principles/appup_cookbook.xml b/system/doc/design_principles/appup_cookbook.xml new file mode 100644 index 0000000000..bc61578953 --- /dev/null +++ b/system/doc/design_principles/appup_cookbook.xml @@ -0,0 +1,627 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2003</year><year>2009</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>Appup Cookbook</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>appup_cookbook.xml</file> + </header> + <p>This chapter contains examples of <c>.appup</c> files for + typical cases of upgrades/downgrades done in run-time.</p> + + <section> + <title>Changing a Functional Module</title> + <p>When a change has been made to a functional module, for example + if a new function has been added or a bug has been corrected, + simple code replacement is sufficient.</p> + <p>Example:</p> + <code type="none"> +{"2", + [{"1", [{load_module, m}]}], + [{"1", [{load_module, m}]}] +}.</code> + </section> + + <section> + <title>Changing a Residence Module</title> + <p>In a system implemented according to the OTP Design Principles, + all processes, except system processes and special processes, + reside in one of the behaviours <c>supervisor</c>, + <c>gen_server</c>, <c>gen_fsm</c> or <c>gen_event</c>. These + belong to the STDLIB application and upgrading/downgrading + normally requires an emulator restart.</p> + <p>OTP thus provides no support for changing residence modules + except in the case of <seealso marker="#spec">special processes</seealso>.</p> + </section> + + <section> + <title>Changing a Callback Module</title> + <p>A callback module is a functional module, and for code + extensions simple code replacement is sufficient.</p> + <p>Example: When adding a function to <c>ch3</c> as described in + the example in <seealso marker="release_handling#appup">Release Handling</seealso>, <c>ch_app.appup</c> looks as follows:</p> + <code type="none"> +{"2", + [{"1", [{load_module, ch3}]}], + [{"1", [{load_module, ch3}]}] +}.</code> + <p>OTP also supports changing the internal state of behaviour + processes, see <seealso marker="#int_state">Changing Internal State</seealso> below.</p> + </section> + + <section> + <marker id="int_state"></marker> + <title>Changing Internal State</title> + <p>In this case, simple code replacement is not sufficient. + The process must explicitly transform its state using the callback + function <c>code_change</c> before switching to the new version + of the callback module. Thus synchronized code replacement is + used.</p> + <p>Example: Consider the gen_server <c>ch3</c> from the chapter + about the <seealso marker="gen_server_concepts#ex">gen_server behaviour</seealso>. The internal state is a term <c>Chs</c> + representing the available channels. Assume we want add a counter + <c>N</c> which keeps track of the number of <c>alloc</c> requests + so far. This means we need to change the format to + <c>{Chs,N}</c>.</p> + <p>The <c>.appup</c> file could look as follows:</p> + <code type="none"> +{"2", + [{"1", [{update, ch3, {advanced, []}}]}], + [{"1", [{update, ch3, {advanced, []}}]}] +}.</code> + <p>The third element of the <c>update</c> instruction is a tuple + <c>{advanced,Extra}</c> which says that the affected processes + should do a state transformation before loading the new version + of the module. This is done by the processes calling the callback + function <c>code_change</c> (see <c>gen_server(3)</c>). The term + <c>Extra</c>, in this case [], is passed as-is to the function:</p> + <marker id="code_change"></marker> + <code type="none"> +-module(ch3). +... +-export([code_change/3]). +... +code_change({down, _Vsn}, {Chs, N}, _Extra) -> + {ok, Chs}; +code_change(_Vsn, Chs, _Extra) -> + {ok, {Chs, 0}}.</code> + <p>The first argument is <c>{down,Vsn}</c> in case of a downgrade, + or <c>Vsn</c> in case of an upgrade. The term <c>Vsn</c> is + fetched from the 'original' version of the module, i.e. + the version we are upgrading from, or downgrading to.</p> + <p>The version is defined by the module attribute <c>vsn</c>, if + any. There is no such attribute in <c>ch3</c>, so in this case + the version is the checksum (a huge integer) of the BEAM file, an + uninteresting value which is ignored.</p> + <p>(The other callback functions of <c>ch3</c> need to be modified + as well and perhaps a new interface function added, this is not + shown here).</p> + </section> + + <section> + <title>Module Dependencies</title> + <p>Assume we extend a module by adding a new interface function, as + in the example in <seealso marker="release_handling#appup">Release Handling</seealso>, where a function <c>available/0</c> is + added to <c>ch3</c>.</p> + <p>If we also add a call to this function, say in the module + <c>m1</c>, a run-time error could occur during release upgrade if + the new version of <c>m1</c> is loaded first and calls + <c>ch3:available/0</c> before the new version of <c>ch3</c> is + loaded.</p> + <p>Thus, <c>ch3</c> must be loaded before <c>m1</c> is, in + the upgrade case, and vice versa in the downgrade case. We say + that <c>m1</c><em>is dependent on</em><c>ch3</c>. In a release + handling instruction, this is expressed by the element + <c>DepMods</c>:</p> + <code type="none"> +{load_module, Module, DepMods} +{update, Module, {advanced, Extra}, DepMods}</code> + <p><c>DepMods</c> is a list of modules, on which <c>Module</c> is + dependent.</p> + <p>Example: The module <c>m1</c> in the application <c>myapp</c> is + dependent on <c>ch3</c> when upgrading from "1" to "2", or + downgrading from "2" to "1":</p> + <code type="none"> +myapp.appup: + +{"2", + [{"1", [{load_module, m1, [ch3]}]}], + [{"1", [{load_module, m1, [ch3]}]}] +}. + +ch_app.appup: + +{"2", + [{"1", [{load_module, ch3}]}], + [{"1", [{load_module, ch3}]}] +}.</code> + <p>If <c>m1</c> and <c>ch3</c> had belonged to the same application, + the <c>.appup</c> file could have looked like this:</p> + <code type="none"> +{"2", + [{"1", + [{load_module, ch3}, + {load_module, m1, [ch3]}]}], + [{"1", + [{load_module, ch3}, + {load_module, m1, [ch3]}]}] +}.</code> + <p>Note that it is <c>m1</c> that is dependent on <c>ch3</c> also + when downgrading. <c>systools</c> knows the difference between + up- and downgrading and will generate a correct <c>relup</c>, + where <c>ch3</c> is loaded before <c>m1</c> when upgrading but + <c>m1</c> is loaded before <c>ch3</c> when downgrading.</p> + </section> + + <section> + <marker id="spec"></marker> + <title>Changing Code For a Special Process</title> + <p>In this case, simple code replacement is not sufficient. + When a new version of a residence module for a special process + is loaded, the process must make a fully qualified call to + its loop function to switch to the new code. Thus synchronized + code replacement must be used.</p> + <note> + <p>The name(s) of the user-defined residence module(s) must be + listed in the <c>Modules</c> part of the child specification + for the special process, in order for the release handler to + find the process.</p> + </note> + <p>Example. Consider the example <c>ch4</c> from the chapter about + <seealso marker="spec_proc#ex">sys and proc_lib</seealso>. + When started by a supervisor, the child specification could look + like this:</p> + <code type="none"> +{ch4, {ch4, start_link, []}, + permanent, brutal_kill, worker, [ch4]}</code> + <p>If <c>ch4</c> is part of the application <c>sp_app</c> and a new + version of the module should be loaded when upgrading from + version "1" to "2" of this application, <c>sp_app.appup</c> could + look like this:</p> + <code type="none"> +{"2", + [{"1", [{update, ch4, {advanced, []}}]}], + [{"1", [{update, ch4, {advanced, []}}]}] +}.</code> + <p>The <c>update</c> instruction must contain the tuple + <c>{advanced,Extra}</c>. The instruction will make the special + process call the callback function <c>system_code_change/4</c>, a + function the user must implement. The term <c>Extra</c>, in this + case [], is passed as-is to <c>system_code_change/4</c>:</p> + <code type="none"> +-module(ch4). +... +-export([system_code_change/4]). +... + +system_code_change(Chs, _Module, _OldVsn, _Extra) -> + {ok, Chs}.</code> + <p>The first argument is the internal state <c>State</c> passed from + the function <c>sys:handle_system_msg(Request, From, Parent, Module, Deb, State)</c>, called by the special process when + a system message is received. In <c>ch4</c>, the internal state is + the set of available channels <c>Chs</c>.</p> + <p>The second argument is the name of the module (<c>ch4</c>).</p> + <p>The third argument is <c>Vsn</c> or <c>{down,Vsn}</c> as + described for + <seealso marker="#code_change">gen_server:code_change/3</seealso>.</p> + <p>In this case, all arguments but the first are ignored and + the function simply returns the internal state again. This is + enough if the code only has been extended. If we had wanted to + change the internal state (similar to the example in + <seealso marker="#int_state">Changing Internal State</seealso>), + it would have been done in this function and + <c>{ok,Chs2}</c> returned.</p> + </section> + + <section> + <marker id="sup"></marker> + <title>Changing a Supervisor</title> + <p>The supervisor behaviour supports changing the internal state, + i.e. changing restart strategy and maximum restart frequency + properties, as well as changing existing child specifications.</p> + <p>Adding and deleting child processes are also possible, but not + handled automatically. Instructions must be given by in + the <c>.appup</c> file.</p> + + <section> + <title>Changing Properties</title> + <p>Since the supervisor should change its internal state, + synchronized code replacement is required. However, + a special <c>update</c> instruction must be used.</p> + <p>The new version of the callback module must be loaded first + both in the case of upgrade and downgrade. Then the new return + value of <c>init/1</c> can be checked and the internal state be + changed accordingly.</p> + <p>The following <c>upgrade</c> instruction is used for + supervisors:</p> + <code type="none"> +{update, Module, supervisor}</code> + <p>Example: Assume we want to change the restart strategy of + <c>ch_sup</c> from the <seealso marker="sup_princ#ex">Supervisor Behaviour</seealso> chapter from one_for_one to one_for_all. + We change the callback function <c>init/1</c> in + <c>ch_sup.erl</c>:</p> + <code type="none"> +-module(ch_sup). +... + +init(_Args) -> + {ok, {{one_for_all, 1, 60}, ...}}.</code> + <p>The file <c>ch_app.appup</c>:</p> + <code type="none"> +{"2", + [{"1", [{update, ch_sup, supervisor}]}], + [{"1", [{update, ch_sup, supervisor}]}] +}.</code> + </section> + + <section> + <title>Changing Child Specifications</title> + <p>The instruction, and thus the <c>.appup</c> file, when + changing an existing child specification, is the same as when + changing properties as described above:</p> + <code type="none"> +{"2", + [{"1", [{update, ch_sup, supervisor}]}], + [{"1", [{update, ch_sup, supervisor}]}] +}.</code> + <p>The changes do not affect existing child processes. For + example, changing the start function only specifies how + the child process should be restarted, if needed later on.</p> + <p>Note that the id of the child specification cannot be changed.</p> + <p>Note also that changing the <c>Modules</c> field of the child + specification may affect the release handling process itself, + as this field is used to identify which processes are affected + when doing a synchronized code replacement.</p> + </section> + <marker id="sup_add"></marker> + + <section> + <title>Adding And Deleting Child Processes</title> + <p>As stated above, changing child specifications does not affect + existing child processes. New child specifications are + automatically added, but not deleted. Also, child processes are + not automatically started or terminated. Instead, this must be + done explicitly using <c>apply</c> instructions.</p> + <p>Example: Assume we want to add a new child process <c>m1</c> to + <c>ch_sup</c> when upgrading <c>ch_app</c> from "1" to "2". + This means <c>m1</c> should be deleted when downgrading from + "2" to "1":</p> + <code type="none"> +{"2", + [{"1", + [{update, ch_sup, supervisor}, + {apply, {supervisor, restart_child, [ch_sup, m1]}} + ]}], + [{"1", + [{apply, {supervisor, terminate_child, [ch_sup, m1]}}, + {apply, {supervisor, delete_child, [ch_sup, m1]}}, + {update, ch_sup, supervisor} + ]}] +}.</code> + <p>Note that the order of the instructions is important.</p> + <p>Note also that the supervisor must be registered as + <c>ch_sup</c> for the script to work. If the supervisor is not + registered, it cannot be accessed directly from the script. + Instead a help function that finds the pid of the supervisor + and calls <c>supervisor:restart_child</c> etc. must be written, + and it is this function that should be called from the script + using the <c>apply</c> instruction.</p> + <p>If the module <c>m1</c> is introduced in version "2" of + <c>ch_app</c>, it must also be loaded when upgrading and + deleted when downgrading:</p> + <code type="none"> +{"2", + [{"1", + [{add_module, m1}, + {update, ch_sup, supervisor}, + {apply, {supervisor, restart_child, [ch_sup, m1]}} + ]}], + [{"1", + [{apply, {supervisor, terminate_child, [ch_sup, m1]}}, + {apply, {supervisor, delete_child, [ch_sup, m1]}}, + {update, ch_sup, supervisor}, + {delete_module, m1} + ]}] +}.</code> + <p>Note again that the order of the instructions is important. + When upgrading, <c>m1</c> must be loaded and the supervisor's + child specification changed, before the new child process can + be started. When downgrading, the child process must be + terminated before child specification is changed and the module + is deleted.</p> + </section> + </section> + + <section> + <title>Adding or Deleting a Module</title> + <p>Example: A new functional module <c>m</c> is added to + <c>ch_app</c>:</p> + <code type="none"> +{"2", + [{"1", [{add_module, m}]}], + [{"1", [{delete_module, m}]}]</code> + </section> + + <section> + <title>Starting or Terminating a Process</title> + <p>In a system structured according to the OTP design principles, + any process would be a child process belonging to a supervisor, + see <seealso marker="#sup_add">Adding and Deleting Child Processes</seealso> above.</p> + </section> + + <section> + <title>Adding or Removing an Application</title> + <p>When adding or removing an application, no <c>.appup</c> file + is needed. When generating <c>relup</c>, the <c>.rel</c> files + are compared and <c>add_application</c> and + <c>remove_application</c> instructions are added automatically.</p> + </section> + + <section> + <title>Restarting an Application</title> + <p>Restarting an application is useful when a change is too + complicated to be made without restarting the processes, for + example if the supervisor hierarchy has been restructured.</p> + <p>Example: When adding a new child <c>m1</c> to <c>ch_sup</c>, as + in the <seealso marker="#sup_add">example above</seealso>, an + alternative to updating the supervisor is to restart the entire + application:</p> + <code type="none"> +{"2", + [{"1", [{restart_application, ch_app}]}], + [{"1", [{restart_application, ch_app}]}] +}.</code> + </section> + + <section> + <marker id="app_spec"></marker> + <title>Changing an Application Specification</title> + <p>When installing a release, the application specifications are + automatically updated before evaluating the <c>relup</c> script. + Hence, no instructions are needed in the <c>.appup</c> file:</p> + <pre> +{"2", + [{"1", []}], + [{"1", []}] +}.</pre> + </section> + + <section> + <title>Changing Application Configuration</title> + <p>Changing an application configuration by updating the <c>env</c> + key in the <c>.app</c> file is an instance of changing an + application specification, <seealso marker="#app_spec">see above</seealso>.</p> + <p>Alternatively, application configuration parameters can be + added or updated in <c>sys.config</c>.</p> + </section> + + <section> + <title>Changing Included Applications</title> + <p>The release handling instructions for adding, removing and + restarting applications apply to primary applications only. + There are no corresponding instructions for included + applications. However, since an included application is really a + supervision tree with a topmost supervisor, started as a child + process to a supervisor in the including application, a + <c>relup</c> file can be manually created.</p> + <p>Example: Assume we have a release containing an application + <c>prim_app</c> which have a supervisor <c>prim_sup</c> in its + supervision tree.</p> + <p>In a new version of the release, our example application + <c>ch_app</c> should be included in <c>prim_app</c>. That is, + its topmost supervisor <c>ch_sup</c> should be started as a child + process to <c>prim_sup</c>.</p> + <p>1) Edit the code for <c>prim_sup</c>:</p> + <code type="none"> +init(...) -> + {ok, {...supervisor flags..., + [..., + {ch_sup, {ch_sup,start_link,[]}, + permanent,infinity,supervisor,[ch_sup]}, + ...]}}.</code> + <p>2) Edit the <c>.app</c> file for <c>prim_app</c>:</p> + <code type="none"> +{application, prim_app, + [..., + {vsn, "2"}, + ..., + {included_applications, [ch_app]}, + ... + ]}.</code> + <p>3) Create a new <c>.rel</c> file, including <c>ch_app</c>:</p> + <code type="none"> +{release, + ..., + [..., + {prim_app, "2"}, + {ch_app, "1"}]}.</code> + + <section> + <title>Application Restart</title> + <p>4a) One way to start the included application is to restart + the entire <c>prim_app</c> application. Normally, we would then + use the <c>restart_application</c> instruction in + the <c>.appup</c> file for <c>prim_app</c>.</p> + <p>However, if we did this and then generated a <c>relup</c> file, + not only would it contain instructions for restarting (i.e. + removing and adding) <c>prim_app</c>, it would also contain + instructions for starting <c>ch_app</c> (and stopping it, in + the case of downgrade). This is due to the fact that + <c>ch_app</c> is included in the new <c>.rel</c> file, but not + in the old one.</p> + <p>Instead, a correct <c>relup</c> file can be created manually, + either from scratch or by editing the generated version. + The instructions for starting/stopping <c>ch_app</c> are + replaced by instructions for loading/unloading the application:</p> + <code type="none"> +{"B", + [{"A", + [], + [{load_object_code,{ch_app,"1",[ch_sup,ch3]}}, + {load_object_code,{prim_app,"2",[prim_app,prim_sup]}}, + point_of_no_return, + {apply,{application,stop,[prim_app]}}, + {remove,{prim_app,brutal_purge,brutal_purge}}, + {remove,{prim_sup,brutal_purge,brutal_purge}}, + {purge,[prim_app,prim_sup]}, + {load,{prim_app,brutal_purge,brutal_purge}}, + {load,{prim_sup,brutal_purge,brutal_purge}}, + {load,{ch_sup,brutal_purge,brutal_purge}}, + {load,{ch3,brutal_purge,brutal_purge}}, + {apply,{application,load,[ch_app]}}, + {apply,{application,start,[prim_app,permanent]}}]}], + [{"A", + [], + [{load_object_code,{prim_app,"1",[prim_app,prim_sup]}}, + point_of_no_return, + {apply,{application,stop,[prim_app]}}, + {apply,{application,unload,[ch_app]}}, + {remove,{ch_sup,brutal_purge,brutal_purge}}, + {remove,{ch3,brutal_purge,brutal_purge}}, + {purge,[ch_sup,ch3]}, + {remove,{prim_app,brutal_purge,brutal_purge}}, + {remove,{prim_sup,brutal_purge,brutal_purge}}, + {purge,[prim_app,prim_sup]}, + {load,{prim_app,brutal_purge,brutal_purge}}, + {load,{prim_sup,brutal_purge,brutal_purge}}, + {apply,{application,start,[prim_app,permanent]}}]}] +}.</code> + </section> + + <section> + <title>Supervisor Change</title> + <p>4b) Another way to start the included application (or stop it + in the case of downgrade) is by combining instructions for + adding and removing child processes to/from <c>prim_sup</c> with + instructions for loading/unloading all <c>ch_app</c> code and + its application specification.</p> + <p>Again, the <c>relup</c> file is created manually. Either from + scratch or by editing a generated version. Load all code for + <c>ch_app</c> first, and also load the application + specification, before <c>prim_sup</c> is updated. When + downgrading, <c>prim_sup</c> should be updated first, before + the code for <c>ch_app</c> and its application specification + are unloaded.</p> + <code type="none"> +{"B", + [{"A", + [], + [{load_object_code,{ch_app,"1",[ch_sup,ch3]}}, + {load_object_code,{prim_app,"2",[prim_sup]}}, + point_of_no_return, + {load,{ch_sup,brutal_purge,brutal_purge}}, + {load,{ch3,brutal_purge,brutal_purge}}, + {apply,{application,load,[ch_app]}}, + {suspend,[prim_sup]}, + {load,{prim_sup,brutal_purge,brutal_purge}}, + {code_change,up,[{prim_sup,[]}]}, + {resume,[prim_sup]}, + {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}], + [{"A", + [], + [{load_object_code,{prim_app,"1",[prim_sup]}}, + point_of_no_return, + {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}}, + {apply,{supervisor,delete_child,[prim_sup,ch_sup]}}, + {suspend,[prim_sup]}, + {load,{prim_sup,brutal_purge,brutal_purge}}, + {code_change,down,[{prim_sup,[]}]}, + {resume,[prim_sup]}, + {remove,{ch_sup,brutal_purge,brutal_purge}}, + {remove,{ch3,brutal_purge,brutal_purge}}, + {purge,[ch_sup,ch3]}, + {apply,{application,unload,[ch_app]}}]}] +}.</code> + </section> + </section> + + <section> + <title>Changing Non-Erlang Code</title> + <p>Changing code for a program written in another programming + language than Erlang, for example a port program, is very + application dependent and OTP provides no special support for it.</p> + <p>Example, changing code for a port program: Assume that + the Erlang process controlling the port is a gen_server + <c>portc</c> and that the port is opened in the callback function + <c>init/1</c>:</p> + <code type="none"> +init(...) -> + ..., + PortPrg = filename:join(code:priv_dir(App), "portc"), + Port = open_port({spawn,PortPrg}, [...]), + ..., + {ok, #state{port=Port, ...}}.</code> + <p>If the port program should be updated, we can extend the code for + the gen_server with a <c>code_change</c> function which closes + the old port and opens a new port. (If necessary, the gen_server + may first request data that needs to be saved from the port + program and pass this data to the new port):</p> + <code type="none"> +code_change(_OldVsn, State, port) -> + State#state.port ! close, + receive + {Port,close} -> + true + end, + PortPrg = filename:join(code:priv_dir(App), "portc"), + Port = open_port({spawn,PortPrg}, [...]), + {ok, #state{port=Port, ...}}.</code> + <p>Update the application version number in the <c>.app</c> file + and write an <c>.appup</c> file:</p> + <code type="none"> +["2", + [{"1", [{update, portc, {advanced,port}}]}], + [{"1", [{update, portc, {advanced,port}}]}] +].</code> + <p>Make sure the <c>priv</c> directory where the C program is + located is included in the new release package:</p> + <pre> +1> <input>systools:make_tar("my_release", [{dirs,[priv]}]).</input> +...</pre> + </section> + + <section> + <title>Emulator Restart</title> + <p>If the emulator can or should be restarted, the very simple + <c>.relup</c> file can be created manually:</p> + <code type="none"> +{"B", + [{"A", + [], + [restart_new_emulator]}], + [{"A", + [], + [restart_new_emulator]}] +}.</code> + <p>This way, the release handler framework with automatic packing + and unpacking of release packages, automatic path updates etc. can + be used without having to specify <c>.appup</c> files.</p> + <p>If some transformation of persistent data, for example database + contents, needs to be done before installing the new release + version, instructions for this can be added to the <c>.relup</c> + file as well.</p> + </section> +</chapter> + |