This chapter contains an example of a file for a typical run-time upgrade/downgrade case .appup
.
Change function Module
When you want to make a change to a functional module, for example if you add a new function or correct an error, replacing it with simple code is sufficient.
For example:
{"2", [{"1", [{Load_module, M}]}], [{"1", [{Load_module, M}]}]}.
Change Resident Module
In systems based on the design principle of OTP, all processes, except system processes and special processes, belong to, supervisor
gen_server
or are gen_fsm
one of gen_event
the behaviors. They belong to the Stdlib application, and if an upgrade or downgrade generally requires a simulator reboot.
OTP therefore does not provide support for resident module changes, except for special processes .
Changing the callback module
The callback module is a functional module, which is sufficient for code extensions, as long as simple code substitution is used.
For example: When adding a function to an example in a release process ch3
, you ch_app.appup
should:
{"2", [{"1", [{Load_module, CH3}]}], [{"1", [{Load_module, CH3}]}]}.
OTP also supports changing the internal state of the behavior process, see Change internal state below.
Change the internal state
In this case, simple code substitution is not enough. The process must explicitly use the callback function to transform its state before switching to the new version of the callback module code_change
. This must be replaced with synchronous code.
For example: Think of the gen_server from the gen_server Act chapter ch3
. The internal state is a value that represents the available channel Chs
. Suppose we want to add a counter N
to track the current alloc
number of requests. This means that we need to change the format to {Chs, N}
.
.appup
The file can be:
{"2", [{"1", [{update, CH3, {Advanced, []}}], [{"1", [{update, CH3, {Advanced, []}}]}.
update
The third element of the directive is a tuple {advanced, Extra}
, which says that the affected process is going to make a state transition before loading the new version of the module. The process invokes the callback function code_change
(see gen_server(3)
). Extra
, where [], is passed to the function as is:
-module (CH3) ....-export ([CODE_CHANGE/3]) .... code_change ({down, _vsn}, {Chs, N}, _extra) {OK, chs};code_ Change (_VSN, CHS, _extra), {OK, {CHS, 0}}.
If it is degraded, then the first parameter is {down,Vsn}
, if it is an upgrade, then yes Vsn
. Expressions Vsn
are obtained from the "original" version of the module, that is, which version is upgraded, or which version to downgrade to.
The version is defined by the module properties- vsn
-if any. ch3
There is no such attribute in, in this case is the checksum of the beam file (a large integer), this value is not interesting, so ignore it here.
( ch3
the other callback functions also need to be modified, and there may be a need to add a new excuse function, which is not shown here).
Module dependencies
Suppose we extend a module by adding a new interface function, as in the case of release processing , where ch3
a function is added available/0
.
Suppose that if we were to m1
add a call to the function in the module, m1
ch3
ch3:available/0
a run-time error would occur if the new version was loaded first and called before the new version was loaded.
Therefore, in the case of an upgrade, it must be loaded before, or vice versa, in the case ch3
m1
of a downgrade. In this way, we say that m1
it is dependent ch3
. In the release processing instructions, this is expressed through DepMods
the element:
{load_module, module, depmods} {update, Module, {advanced, Extra}, Depmods}
DepMods
is the Module
list of modules that are dependent on.
For example, a module in an application myapp
m1
is dependent when it is upgraded from "1" to "2" or from "2" to "1" ch3
:
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}]}]}.
If m1
and ch3
belong to the same application, the .appup
file should be as follows:
{"2", [{"1", [{load_module, CH3}, {Load_module, M1, [CH3]}]}], [{"1", [{load_module, CH3}, {Load_ module, M1, [CH3]}]}]}.
Note that when you downgrade, you still m1
depend on it ch3
. systools
know the difference between upgrade and downgrade and generate the correct relup
one, where it is ch3
m1
loaded before the upgrade, and when it is degraded m1
ch3
.
Code to change special processes
In this case, simple code substitution is not enough. When a new version of a special process's resident module is loaded, the process must make a fully qualified call to its looping function to switch to the new code. This must be replaced with synchronous code.
Note
For special processes, the name of the user-defined resident module must be listed in the child process specification Modules
so that the release processor can find the process.
For example. Consider the example in the sys and Proc_lib chapter ch4
. The sub-process specification should be as follows when booting through the path:
{CH4, {CH4, Start_link, []}, permanent, Brutal_kill, worker, [CH4]}
If it ch4
is part of the app, sp_app
and when the app is upgraded from version "1" to "2", ch4
a new version to load sp_app.appup
should be as follows:
{"2", [{"1", [{update, CH4, {advanced, []}}], [{"1", [{update, CH4, {advanced, []}}]}.
update
directive must contain a tuple {advanced,Extra}
. The directive will let a special process invoke the callback function, which the system_code_change/4
user must implement. The expression Extra
, here is [], is passed to system_code_change/4
:
-module (CH4) ....-export ([SYSTEM_CODE_CHANGE/4]) .... System_code_change (Chs, _module, _OLDVSN, _extra), {OK , CHS}.
The first parameter is an internal state State
, from a function sys:handle_system_msg(Request, From, Parent, Module, Deb, State)
that is called by a special process when a particular process receives a system message. In ch4
, the internal state is a collection of available channels Chs
.
The second parameter is the name of the module ( ch4
).
The third parameter is Vsn
either {down,Vsn}
, as described in gen_server:code_change/3 .
Here, all parameters are ignored except the first one, and the function simply returns to the internal state. If the code is just being expanded, then that's enough. If we also want to change the internal state (similar to the example in changing the internal state), we can do it in this function and return it {ok,Chs2}
.
Change of the governor's path
Octave to support changing the internal state, that is, changing the restart policy and maximum restart frequency properties, and changing the existing child process specifications.
You can also add and remove child processes, but this is not handled automatically. The instruction must be given in the .appup
file.
Change properties
Because the governor needs to change its internal state, it must be replaced with synchronous code. However, special instructions must be used update
.
The new version of the callback module must be loaded first, whether in the case of an upgrade or a downgrade. You can then check init/1
the new return value and change the internal state accordingly.
The following directives are used for the Governor upgrade
:
{Update, Module, supervisor}
For example, suppose we want to change the restart policy from the Supervisor Behavior Chapter to ch_sup
one_for_one
one_for_all
. We want to modify ch_sup.erl
the callback function in init/1
:
-module (Ch_sup) .... Init (_args) , {OK, {one_for_all, 1, 60}, ...}.
File ch_app.appup
:
{"2", [{"1", [{update, CH_SUP, supervisor}]}], [{"1", [{update, CH_SUP, supervisor}]}]}.
Change sub-process specifications
When changing an existing sub-process specification, the directive-including the .appup
file-is the same as the change attribute described above:
{"2", [{"1", [{update, CH_SUP, supervisor}]}], [{"1", [{update, CH_SUP, supervisor}]}]}.
The changes do not affect existing child processes. For example, changing the startup function will only specify how the child process should restart if a restart is required later.
Note that the ID of the child process specification can not be changed.
Also note that changing the field of the child process specification Modules
may affect the release process itself, because it is used to identify which processes are affected in synchronous code substitution.
Adding and removing child processes
As stated earlier, changing the child process specification does not affect existing child processes. The new sub-process specification will be automatically added but will not be deleted. Also, the child process does not start or terminate automatically, so you must explicitly use the apply
directive.
For example: Suppose ch_app
we want to ch_sup
add a new subprocess when we upgrade from "1" to "2" m1
. This also means that if you downgrade from "2" to "1", m1
you will be removed.
{"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} ]}]}.
It is important to note the order of instructions.
Also note that the process must be registered as ch_sup
the script to function correctly. If the path is not registered, it cannot be accessed directly from the script. This must be done by writing a helper function to find the PID of the path and calling it supervisor:restart_child
, and you must use apply
the instruction to invoke the function in the script.
If a ch_app
module is introduced in the version "2", m1
it must also be loaded during the upgrade and deleted when it is degraded:
{"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} ]}]}.
It is important to note again the order of instructions. When you upgrade, you must load m1
and change the child process specification of the thread before you can start a new child process. When demoted, the child process must terminate before changing the specification of the child process and deleting the module.
Add or Remove modules
For example, ch_app
a new feature module is added m
:
{"2", [{"1", [{Add_module, M}]}], [{"1", [{Delete_module, M}]}]
Start or terminate a process
In a system built on the principle of OTP design, any process is a subprocess of a certain path, see Adding and removing child processes earlier.
Add or remove an app
When you add or remove an app, you don't need any .appup
files. When generated relup
, files are compared .rel
and automatically added add_application
and remove_application
instructed.
Restart your app
When changes are too complex to be done without restarting the process, restarting the application can be useful, for example, when the monitoring hierarchy changes.
For example: When ch_sup
adding a new subprocess as m1
shown in The example above , another way to do this is to restart the entire application without updating the process:
{"2", [{"1", [{restart_application, Ch_app}]}], [{"1", [{restart_application, Ch_app}]}]}.
Change Application Specifications
When you install a publication, the application specifications are relup
automatically updated before the script is executed. Therefore, no .appup
directives are required in the file:
{"2", [{"1", []}], [{"1", []}]}.
Change app Configuration
Changing the .app
application configuration by updating the key in the file env
is actually a special case of changing the application specification, see the previous section .
Alternatively, you can sys.config
add or change application configuration parameters in the file.
Change the included application
The publish processing instructions for adding, deleting, and restarting an app can only be used for the main app. There is no corresponding instruction for the included app. However, because a contained application is actually a supervised tree with the highest supervisor, and is initiated as a subprocess of a certain process in the included application, you can create a relup
file manually.
For example: Suppose we have a release that contains an application prim_app
, and it has a supervisor tree in it prim_sup
.
In the new version of the release, our sample application ch_app
is to be included in the prim_app
. In other words, its highest path is to be prim_sup
started as a child process.
- Edit
prim_sup
The code:
Init (...), { OK, {... supervisor flags ..., [..., {ch_sup, {ch_sup,start_link,[]}, Permanent , Infinity,supervisor,[ch_sup]}} .
- Edited
prim_app
.app
file:
{application, Prim_app, [..., {VSN, "2"}, ..., {included_applications, [Ch_app]}, ...]}.
- Create a new
.rel
file that contains ch_app
:
{release, ..., {prim_app, "2"}, {ch_app, "1"}]}.
App Restart
4.a. One way to start a contained app is to restart the entire prim_app
app. In general, we want prim_app
to .appup
use directives in the file restart_application
.
However, if we do this and generate a relup
file, it will not only contain a reboot (that is, remove and add) prim_app
, it will also contain the boot ch_app
(which is stopped in the case of demotion). This is because it is ch_app
also included in the new .rel
file, while the old one is not.
So, the correct relup
files are created manually, either from scratch or by editing the generated version. Start/stop the instructions for Ch_app
are replaced with the instructions for loading/unloading the application:
{"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,bruta L_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,sto P,[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_s Up,brutal_purge,brutal_purge}}, {Purge,[prim_app,prim_sup]}, {Load,{prim_app,brutal_purge,brutal_purge}}, {Load,{prim_sup,brutal_purge,brutal_pu Rge}}, {Apply,{application,start,[prim_app,permanent]}]}]}.
Change of the governor's journey
4.b. Another approach is prim_sup
to start the application by combining instructions for adding and removing child processes and loading/unloading all ch_app
code and application Specifications (stopping in the case of demotion).
This time, you still have to create the relup
file manually. You can start writing or editing the generated version. First, before updating prim_sup
, load ch_app
all the code and load the application specifications. When downgrading, it prim_sup
should be updated ch_app
before the code and application specifications are unloaded.
{"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]}}]}]}.
Change non-Erlang code
Changing the Code of a program written in a programming language other than Erlang, such as a port program, is closely related to the application, and OTP does not provide special support for this.
For example, change the code of the Port program: Assume that the Erlang process that controls the port is a gen_server portc
and that the port is open in a callback function init/1
:
Init (...) , portprg = Filename:join (Code:priv_dir (APP), "PORTC"), Port = Open_port ({spawn, PORTPRG}, [...]), ..., {OK, #state {port=port, ...}}.
If you want to update the port program, then we can extend the Gen_server code, add a close old port and open the new port. (if necessary, Gen_server can request the data to be saved from the port program and then transfer the data to the new port):
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, ...}}.
.app
Update the app version in the file and write a .appup
file:
["2", [{"1", [{update, PORTC, {Advanced,port}}]}], [{"1", [{update, PORTC, {advanced,port}]}]].
Make sure that the directory where the C program resides is priv
also included in the new release package:
1> Systools:make_tar ("My_release", [{Dirs,[priv]}]) ....
Simulator Reset
If the emulator needs to be restarted, just create a very simple file manually .relup
:
{"B", [{"A", [ ], [Restart_new_emulator]}], [{"A", [], [Restart_new_emulator]}]}.
This allows you to use a publishing processor framework that comes with automatic packaging, unpacking, automatic path updates, and so on, without specifying a .appup
file.
If you want to convert some persisted data to a process, such as the database content, before installing a new release, its instructions can be added directly to the .relup
file.
12.Appup Cookbook