4 Release Handling
4.1 Introduction
A new release is produced whenever a change of the system is made. The release consists of a release packet which is identified by its name. The release package is installed in a running system by giving commands to the release handler, which is an SASL process. The system has a unique version string, which is updated whenever a new release is installed. This version string is referred to as the system version. This is the version of the entire system, not just the OTP version. If the system consists of several nodes, each node has its own system version. Release handling can be synchronized between nodes, or be done at one node at a time.
Some changes require the node to be brought down. If this is the case and the system consists of several nodes, the release upgrade can be implemented as follows:
- move all applications from the node to be changed to other nodes
- take down the node
- do the change
- restart the node and move the applications back.
There are several different types of releases:
- Operating system change.
- This change can only be made by taking down the node. The installation is not supported by the release handler and is performed manually. It is not possible to roll back to a previous release automatically if there is an error.
- Application code or data change.
- This is the normal type of release. The release is installed without bringing down the running node. Some type of changes, for example change of C-programs, may be done by shutting down and restarting the affected processes.
- Erlang emulator change.
- This change can only be made by taking down the node. However, the release handler supports this type of change and it is done as a normal release.
4.2 Administering Releases
This section describes how to build and install releases. Also refer to the Reference Manual, the section SASL,
release_handler
for more details. The following steps are involved in administering releases:
- Each node in the system needs a release packet. A release building tool in OTP is used to construct this packet. This tool constructs a release packet from application specification files, code files, data files, and a file which describes how the release is installed in the system. The release building tool is invoked with commands in the module
systools
.- The release packet must be transferred to the target machine. This can be done with a method like ftp.
- After transfer to the target machine, the release packet must be unpacked. The unpacking makes the system version in the release packet available for installation. When the system version is installed, a release upgrade script is executed, which loads all new code. If an installation fails in some way, the entire system is restarted from the old version.
- When the installation is complete, the system version must be made permanent. When permanent, the new version is used if the system restarts.
It is also possible to install an old version, or reboot the system from an old version. There are functions to remove old releases from the disk as well.
4.3 File Structure
The file structure used in an OTP system is described in Release Directories. There are two ways of using this file structure together with the release handler.
The simplest way is to store all user-defined applications under
$OTP_ROOT/lib
in the same way as other OTP applications. The release handler takes care of everything, from unpacking a release to the removal of it. The release packets should be stored in the releases directory (default$OTP_ROOT/releases
). This is whererelease_handler:unpack_release/1
looks for the packages, and where the release handler stores its files. Each packet is a compressedtar
file. The files in thetar
file are named relative to the$OTP_ROOT
directory. For example, if a new version (say 1.3) of the applicationsnmp
is contained in the release packet, the files in thetar
file should be namedlib/snmp-1.3/*
.The second way is to store all user-defined applications in some other place in the file system. In this case, some more work has to be done outside the release handler. Specifically, the release packages must be unpacked in some way and the release handler must be notified of where the new release is located. The following three functions are available in the module
release_handler
and can be used with this method:
set_unpacked/2
set_removed/1
install_file/2
.4.4 Release Installation Files
The following files must be present when a release is installed. All file names are relative to the releases directory. The location of this directory is specified with the configuration parameter
releases_dir
(default$OTP_ROOT/releases
). In a target system, the default location is preferred, but during testing it may be more convenient to let the release handler write its files at a user specified directory, than in the$OTP_ROOT
directory.The files are either present in the release packet, or generated at the target machine and copied to their correct places using
release_handler:install_file/2
.
ReleaseName.rel
Vsn/relup
Vsn/start.boot
Vsn/sys.config
Vsn
is the system version string, andEVsn
is the version of the Erlang runtime system.4.4.1 ReleaseName.rel
This file contains the new name and version of the system, the version of
erts
(the Erlang runtime system) and the libs which are used in the release. The file must contain one Erlang term:{release, {Name, Vsn}, {erts, EVsn}, [{App, AVsn} | {App, AVsn, AType} | {App, AVsn, [App]} | {App, AVsn, AType, [App]}]}.
Name
,Vsn
,EVsn
andAVsn
are strings,App
andATytpe
are atoms.ReleaseName
is a string given in the call torelease_handler:unpack_release(ReleaseName)
.Name
is the name of the system (the same as found in the boot file). This file is further described in Release Structure.4.4.2 relup
This file contains instructions on how to install this version in the system. This file must contain one Erlang term:
{Vsn, [{FromVsn, Descr, RuScript}], [{ToVsn, Descr, RuScript}]}.
Vsn
,FromVsn
andToVsn
are strings,RuScript
is a release upgrade script.Descr
is a user defined parameter which is not processed by any release handling functions. It can be used to describe the release to an operator. Eventually, it will be returned byrelease_handler:install_release/1
andrelease_handler:check_install_release/1
. There is one tuple{FromVsn, Descr, RuScript}
for each old system version which can be upgraded to this version, and one tuple{ToVsn, Descr, RuScript}
for each old version that this version can be downgraded to.When upgrading from
FromVsn
withrelease_handler:install_release/1
, there does not have to be an exact match of versions.FromVsn
can be a sub-string of the current version of the system. For example, if the current version is"2.1.1"
, we can upgrade fromFromVsn
"2.1"
or"2.1.1"
, but not from"2.0"
or"2.1.1.2"
. However, if this scheme is used, the same release upgrade script is used to go from both"2.1"
and"2.1.1"
. Therefore,"2.1.1"
must be compatible with"2.1"
. If you do not want to use this feature, you must make sure that the current version and the new version match before you callinstall_release/1
.4.4.3 start.boot
This file is the compiled
start.script
file. It is used to boot the Erlang machine.4.4.4 sys.config
This file is the system configuration file.
4.5 Release Handling Principles
The following sections describe the principles for updating parts of an OTP system.
4.5.1 Erlang Code
One of the advantages of Erlang is its capability to change code during runtime. This is a somewhat unique feature. However, changing code during runtime cannot be done without considering the application.
The code change feature in Erlang is made possible because Erlang allows two versions of a module to be present in the system. The latest version is called current and previous version is called old. Any global call to a function, which is a call made by
apply
or pre-fixed with the module name, is always made to the current version of a module. Local calls, which are calls to functions within the same module, always refer to the version of the module in which the call is written.A process is said to have references to particular versions of a module. A process, of course, has a reference to the version of the module where the function it currently executes is defined, but it also has references to the module versions of all stacked, but not yet finished calls.
Before a new version of a module can be loaded, the current version must be made old. If a previous old version does not exist, this can be done easily. All processes which execute the version which is now old will continue to do so until they have no unfinished calls within the old version. The new version is loaded at the same time as the previous version is made old, and there is no gap when there is no current version. This is all taken care of by Erlang.
If an old version already exists, this version must first be purged to make room for the current version to become old. However, a version cannot be purged if processes still have references to it. If this is the case, these processes must either be terminated, or the loading of the new release must be postponed until these processes have terminated by themselves or no longer have references to the old version. This behaviour can be configured in the release upgrade script for each module.
To prevent processes from making calls to other processes during the release installation, they may be suspended. All processes implemented with the standard behaviours, or with
sys
, can be suspended. The process enters a special suspend loop instead of its usual main process loop. In the suspend loop, the process can only receive system messages and shut-down messages from its supervisor. The code change message is a special system message, and this message causes the process to change code to the new version, and maybe to transform its internal state. A suspended process can be resumed to continue its execution, possibly in a new module.There are three different types of modules.
- Functional module.
- A functional module is a module which does not contain a process loop, which means that no process has constant references to a functional module.
lists
is an example of a functional module.- Process module.
- A process module is a module which contains a process loop, which means that some process has constant references to this module.
init
is an example of a process module.- Callback module.
- A callback module is a special case of a functional module which serves as a callback module for a generic behaviour such as
gen_server
. These modules are quite similar to process modules in the sense that processes are heavily dependent on them.file
is an example of a callback module. In the current implementation, a call to a callback module is always a global call (i.e. it refers to the latest version of the module). This has some implications on how updates are handled.These types of modules are handled differently when changing code.
4.5.1.1 Functional Module
Normally, it is quite easy to change a functional module. If the API is backwards compatible, as may be the case with bug fixes or new functionality, we can simply load the new version. After a short while, when no processes have references to the old version, the old module is purged.
A more tricky situation arises if the API of a functional module is changed so that it is no longer backwards compatible. We must make sure that no processes, directly or indirectly, try to call the functions which have changed. We can do this by suspending the processes. Then, all other modules which are changed to reflect the API change are loaded, the new version of our module is loaded, and finally all the suspended processes are resumed. There are two alternatives available to manage this type of change:
- Try to find all calls to the module, change them, and write dependencies in your release upgrade script. This may be possible if the function is not called from many other functions.
- Avoid this type of change. This is the only possible solution if the function is called from many other modules. Instead, introduce a new function and keep the old version for backward compatibility. In the next release, when all other modules are changed as well, you can delete the old version of the function.
4.5.1.2 Process Module
A pure process module never contains global calls to itself. Therefore, the new module can be loaded and all processes which run this module are told to change their code and, if required, to transform their internal state.
In practice, few modules are pure in the sense that they never contain global calls to themselves. If you use higher-order functions such as
lists
in a module, or if you ever use afun
, there will be global calls to the module. Therefore, we cannot just load the module because a process might make a spontaneous change to the new version of the module without transforming its state. If the change was totally backwards compatible, as in the case of a bug fix, we can load the module and hope that a process does not run into the buggy code before it performs a spontaneous code change, or we can send a code change message to the process in question. Alternatively, suspend all processes which run the code, tell them to change code, and then resume them.4.5.1.3 Callback Module
As long as the internal state in a callback module has not changed, we can load the new version of the module. This is the same situation that applies to functional modules.
If the internal state has been changed, we must first suspend the processes, tell them to change code and at the same time give them the possibility to transform their states, and finally resume them. This is similar to the situation that applies to process modules.
4.5.1.4 Dependencies Among Processes
It is possible that a group of processes which communicate must perform code changes while they are suspended. Some of the processes may otherwise use the old protocol while others use the new protocol. On the other hand, there may be time-out dependencies which restrict the number of processes which can perform a synchronized code change as one set. The more processes that are included in the set, the longer the processes are suspended.
There may also be problems with circular dependencies. The following scenario illustrates this situation.
- two modules
a
andb
are dependent on each other- each module is executed by one process with the same name as the corresponding module
- both are updated at the same time because the internal protocol between them has changed.
The following sequence of events may occur:
a
is suspended.- the release handler then tries to suspend
b
, but some microsecond before this happens,b
tries to communicate witha
which is now suspended- If
b
hangs in its call toa
, the suspension ofb
fails and onlya
is updated.- If
b
notices thata
does not answer and is able to deal with it, thenb
receives the suspend message and is suspended. Then both modules are updated and resumed.- When
a
resumes, there is a message waiting fromb
. This message may be of an old format whicha
does not recognize.Situations of the type described, and many others, are highly application dependent. The author of the release upgrade script has to predict and avoid them. If the consequences are too difficult to estimate, it may be better to entirely shut down and restart all affected subsystems. This reduces the problem to introducing new code and removes the need to do a synchronized change.
4.5.1.5 Finding Processes
The release handler finds all processes for you. You only have to specify which modules you want to change, and how to change them, and the release handler searches for the processes. To do this, it checks all processes in the application supervision trees. A supervisor must include a process specification for each child, which lists all modules that the child uses. The release handler checks this list to find the processes.
4.5.2 Port Programs
A port program runs as an external program in the operating system. (This applies unless the program is a linked-in driver, but then it is part of the emulator and we cannot change its code easily.) OTP has mechanisms for changing the code of a port program as well, but this cannot be done as nicely as with Erlang processes. Actually, if the linked-in driver is dynamically linked in using
ddll
, it is possible to change its code.A port program is changed by sending a special message to the Erlang port controller process. The port controller sends a message to the port program which tells it to terminate and return any data that must survive the termination. When the program has terminated, the new version of the program is started and the termination data is returned from the previous version.
If the Interface Generator (IG) is used, the update is performed automatically by the IG code. An ordinary port controller process which uses
open_port
and communicates directly with the port has to be updated by changing the user code. Refer to Chapter 4 of the Erlang Development Environment User Guide, section C Interface Generator for more details.4.5.3 Application Specification and Configuration Parameters
In each release, the entire application specification is known to the release handler. Before any changes are made to the system, the new configuration parameters are installed. After that the new release is running the applications will be informed of any changed, new or removed configuration parameters, refer to Chapter KERNEL Reference Manual, application(Module), of the Erlang Environment Refence Manual. This means that old processes may read new parameters before they are informed of the new release. We recommend against the immediate removal of the old parameters. Neither do we recommend that they be syntactically changed, although they may of course change their values. They can be safely removed in the next release, by which time you know that no processes will read the old parameters.
4.5.4 Mnesia Data or Schema Changes
The problem with this type of change is the same problem that exists with changing functional modules. Many processes may read or write in the same tables at the same time. If we change a table definition, we must make sure that all code which uses the table changes its code in a synchronized manner.
To best handle this problem, let one process be responsible for one or many tables. This process creates the tables and changes the table definitions or table data. In this way, you can connect a table with a module. When the process performs a code change, the table may be changed.
4.5.5 Upgrade vs. Downgrade
When a new release is installed, the system is updraged to the new release. The release handler reads the
relup
file in the new release, and finds the upgrade script that corresponds to an upgrade from the current version to the new version of the system. If an old release is installed, the release handler reads therelup
in the current release, and finds the downgrade script that corresponds to to an downgrade from the current version to the old version (that is to be installed) of the system. Therefore, when constructing arelup
file for a new release, there must one upgrade script and one downgrade script for each old version (provided you want to be able to perform soft downgrade of course - an alternative could be to reboot the system from the old release; in that case you don't need a downgrade script).For each modified module in the new release, there are some instructions that specifies how to install that module in a system. When performing an upgrade, the following steps are typically involved:
- Suspend the processes running the module
- Load the new code
- Tell the processes to change code. This usually involves calling a
code_change
function in the new module, which is responsible for state updates, e.g. transforming the state from the old format to the new.- Resume the processes
The code change step is always performed when the new code has been loaded. The reason for this is that it is always the new version of the module that knows how to change the state from the old version. When performing a downgrade the situation is different. The old module does not know how to tranform the new state to the old version - the new format is unknown to the old code! Therefore, it is the responsibility of the new code to revert the state back to the old version during downgrade. The following steps are involved:
- Suspend the processes running the module
- Tell the processes to change the internal state. This usually involves calling a
code_change
function in the current module, which is responsible for state reversals, e.g. transforming the state from the current format to the old.- Load the new code
- Tell the processes to switch code
- Resume the processes
We note that for a process module, it is possible to load the code before the processes change their internal state (since a process module never contains global calls to itself), thus making the steps needed for downgrade almost the same as for upgrade. The remaining difference with the upgrade case is when the state transformation is performed.
For a callback module it isn't actually necessary to tell the processes to switch to the new code, since all calls to the callback module are global calls. The only difference with the upgrade case the order between the load and state change steps.
The difference between how process modules and a callback modules are handled in the dowgrade case comes from the fact that a process module never contains global calls to itself. The code is thus static in the sense that a process executing this module doesn't spontaneously switches to new loaded code. The opposite situation is a dynamic module, where a process executing the module spontaneously switches to the new code when it is loaded. Currently, a callback module is always dynamic, and a process module static. A functional module is always dynamic. If you want to handle the downgrade case, this must be specified in the downgrade script.
4.6 Release Handling Instructions
This section describes the release upgrade script. A release upgrade script is a list of instructions which are interpreted by the release handler when a new system version is installed.
There are two levels of instructions: the high-level instructions and the low-level instructions. High- and low-level instructions may be mixed in one script. However, the high-level instructions are translated to low-level instructions at compile time, because the release handler only understands low-level instructions.
High-level instructions should be written in an
.appup
file for each application. When a release packet is constructed,systools:make_relup
is called to generate arelup
file with low-level instructions.4.6.1 High-level Instructions
The high-level instructions are defined as follows:
{update, Module, Change, PrePurge, PostPurge, [Mod]} | {update, Module, Timeout, Change, PrePurge, PostPurge,[Mod]} | {update, Module, ModType, Timeout, Change, PrePurge, PostPurge,[Mod]}
This instruction is used to update a process module or a callback module. All processes which run the code are suspended, and if the change was
Module = atom()
Timeout = default | infinity | int() > 0
ModType = static | dynamic
Change = soft | {advanced, Extra}
PrePurge = soft_purge | brutal_purge
PostPurge = soft_purge | brutal_purge
Mod = atom()
. If the module is dependent on changes in other modules, these other modules are listed here.advanced
they may transform their states into the new states. Then the processes are resumed. If the module is dependent on other modules, the release handler will suspend this module before it suspends the other modules. In case of circular dependencies, it will suspend processes in the order that update instructions appear in the script.
soft
means backwards compatible changes andadvanced
means internal data changes, or changes which are not backwards compatible.Extra
is any term, and it is sent to thecode_change
function in all affected modules.
The optional parameterTimeout
defines the time-out for the call tosys:suspend
. It specifies how long to wait for a process to handle a suspend message and get suspended. If no value is specified (ordefault
is given), the default value defined insys
is used.
The optional parameterModType
specifies if the code is static or dynamic, as defined in Upgrade vs. Downgrade above. This information is only necessary if you want to handle soft downgrades. It defaults todynamic
. Note that if this parameter is specified,Timeout
is needed as well.
PrePurge
controls what action to take with processes that are executing an old version of this module. These are processes which are left since an earlier release and normally there are no such processes. If it issoft_purge
and such processes are found, the release will not be installed. In this case, theinstall_release/1
function returns{error, {old_processes, Module}}
. If it isbrutal_purge
, the processes which run old code are killed.
PostPurge
controls what action to take with processes that are executing old code when the new module has been installed. If it issoft_purge
, the release handler will purge the old code when no remaining processes execute the code. If it isbrutal_purge
, the code is purged when the release is made permanent. This kills all processes which are still running the old code.
This instruction could also be used for functional modules. However, no processes will be suspended because no processes will have the functional module as its main module. Therefore, no processes may perform a code change.
{load_module, Module, PrePurge, PostPurge, [Mod]}
This instruction is used to update a functional module or a callback module. This instruction only loads the module. A callback module which must perform a code change, or synchronize by being suspended, should use
Module = atom()
.PrePurge = soft_purge | brutal_purge
PostPurge = soft_purge | brutal_purge
Mod = atom()
. If the module is dependent on changes in other modules, these other modules are listed here.update
instead.
The object code is fetched in the beginning of the release upgrade, but the module is loaded when this instruction occurs.
{add_module, Mod}
This instruction adds a new module to the system. This instruction loads the module.
{remove_application, Appl}
This instruction removes an application. It callsapplication:stop
andapplication:unload
for the application.
{add_application, Appl}
This instruction adds a new application. It callsapplication:load
andapplication:start
for the application.
4.6.2 Low-level instructions
The low-level instructions are defined as follows:
{load_object_code, {Lib, LibVsn, [Module]}}
This instruction reads eachModule
from the libraryLib-LibVsn
as a binary. It does not install the code, it just reads the files. This operation should be placed first in the script in order to read all new code from file. This makes the suspend-load-resume cycle less time consuming. After this operation, the code server is updated with the new version ofLib
. Calls tocode:priv_dir(Lib)
which are made after this operation return the newpriv
dir.
Lib
is normally the application name.
point_of_no_return
If a crash occurs after this line, the system cannot recover and is restarted from the old version. This command must only occur once in a script. It should be placed after allload_object_code
operations, and after user defined checks which are performed withapply
. The functioncheck_install_release/1
tries to evaluate all instructions before this command occurs in the script. Therefore, user defined checks must not have side effects, as they may be evaluated many times.
{load, {Module, PrePurge, PostPurge}}
Before using this instruction, theModule
object code must be loaded withload_object_code
. This instruction makes code out of the binary.PrePurge = soft_purge | brutal_purge
, andPostPurge = soft_purge | brutal_purge
.
{remove, {Module, PrePurge, PostPurge}}
This instruction makes the current version of the module old. After this operation, there is no current version in the system.PrePurge = soft_purge | brutal_purge
, andPostPurge = soft_purge | brutal_purge
.
{purge, [Module]}
This instruction kills all processes which run the old versions of the code and deletes the old versions.
{suspend, [Module | {Module, Timeout}]}
This instruction tries to suspend all processes which executeModule
. If a process does not respond, it is ignored. This may cause the process to die, either because it crashes if and when it spontaneously switches to new code, or as a result of apurge
operation. If noTimeout
is specified (or ifdefault
is given), the default time-out defined in the modulesys
is used.
{code_change, [{Module, Extra}]} | {code_change, Mode, [{Module, Extra}]}
This instruction sends acode_change
system message using the functionchange_code
in the modulesys
with theExtra
argument to the suspended processes which run this code.Mode
is eitherup
ordown
. Default isup
. In case of an upgrade, the message is sent to the suspended process, after the new code is loaded (the new version must contain functions to convert from the old internal state, to the the new internal state). If it is a downgrade, the message is sent to the suspended process, before the new code is loaded (the current version must contain functions to convert from the current internal state, to the the old internal state).
Module
uses theExtra
argument internally in its code change function. Refer to the Reference Manual, modulesys
for further details.
One of the arguments to the functionsys:change_code
isOldVsn
. In the case of an upgrade, this parameter is the attribute-vsn
in the old code, orundefined
if no such attribute was defined. In the case of downgrade, it is the tuple{down, Vsn}
, whereVsn
is the version of the module as defined in the.app
file, orundefined
otherwise.
{resume, [Module]}
This instruction resumes all previously suspended processes which run this code.
{stop, [Module]}
This instruction stops all processes which run this code. This is useful when the simplest way to change code for theModule
is to stop and restart the processes which run the code. If a supervisor is stopped, all its children are stopped as well.
{start, [Module]}
This instruction starts all previously stopped processes which run this code. The processes will keep their places in the supervision tree.
{sync_nodes, Id, [Node] | {M, F, A}}
If{M, F, A}
is specified,apply(M, F, A)
is evaluated and must return a list of nodes. This operation synchronizes the release installation with other nodes. Each node in the list of nodes must evaluate this command, with the sameId
. The node waits for all other nodes to evaluate this operation before execution continues. If a node goes down, this is assumed to be an unrecoverable error and this node is restarted from the old release. There is no time-out for this command. This command may hang forever if a user definedapply
enters an infinite loop at some node. It is up to the user to ensure that theapply
command eventually returns or makes the node crash.
{apply, {M, F, A}}
This instruction applies the function to the arguments. If this function crashes after thepoint_of_no_return
instruction, the system is restarted. If it is evaluated before thepoint_of_no_return
, it is evaluated within a catch. If the function crashes with{'EXIT', Reason}
, the call torelease_handler:install_release/1
returns{error, {'EXIT', Reason}}
. The function could also dothrow({error, Error})
, in which case the call toinstall_release
returns{error, Error}
.
Use this instruction with care as this operation makes it possible to do anything.
restart_new_emulator
This instruction shuts down the current emulator and starts a new one. All processes are terminated gracefully. The new release must still be made permanent when the new emulator is up and running. Otherwise, the old emulator is started in case of a crash. This line should be used if a new emulator is introduced, or if a complete reboot of the system should be done.
4.7 Release Handling Examples
This section includes several examples to illustrate how common upgrade situations are handled.
4.7.1 Update of Erlang Code
Several update examples are shown. Unless otherwise stated, it is assumed that all original modules are in the application
foo
, version"1.1"
, and the updated version is"1.2"
.4.7.1.1 Simple Functional Module
This example assumes a pure functional module. This is a module which has functions without side effects. The original version of the module
lists2
looks as follows:%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(lists2). -vsn(1). -export([assoc/2]). assoc(Key, [{Key, Val} | _]) -> {ok, Val}; assoc(Key, [H | T]) -> assoc(Key, T); assoc(Key, []) -> false.The new version of the module adds a new function:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(lists2). -vsn(2). -export([assoc/2, multi_map/2]). assoc(Key, [{Key, Val} | _]) -> {ok, Val}; assoc(Key, [H | T]) -> assoc(Key, T); assoc(Key, []) -> false. multi_map(Func, [[] | ListOfLists]) -> []; multi_map(Func, ListOfLists) -> [apply(Func, lists:map({erlang, hd}, ListOfLists)) | multi_map(Func, lists:map({erlang, tl}, ListOfLists))].The release upgrade instructions are:
[{load_module, lists2, soft_purge, soft_purge, []}]Alternatively, the low-level instructions are:
[{load_object_code, {foo, "1.2", [lists2]}}, point_of_no_return, {load, {lists2, soft_purge, soft_purge}}]4.7.1.2 A More Complicated Functional Module
This example assumes a functional module which uses
lists2
. If it only uses the old functions, we can load the new version independently oflists2
. The original version looks as follows:%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(bar). -vsn(1). -export([simple/1, complicated_sum/1]). simple(X) -> case lists2:assoc(simple, X) of {ok, Val} -> Val; false -> false end. complicated_sum([X, Y, Z]) -> cs(X, Y, Z). cs([HX | TX], [HY | TY], [HZ | TZ]) -> NewRes = cs(TX, TY, TZ), [HX + HY + HZ | NewRes]; cs([], [], []) -> [].The new version of the module uses the new functionality of
lists2
in order to simplify the implementation of the useful functioncomplicated_sum/1
. It does not change the API in any way.%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(bar). -vsn(2). -export([simple/1, complicated_sum/1]). simple(X) -> case lists2:assoc(simple, X) of {ok, Val} -> Val; false -> false end. complicated_sum(X) -> lists2:multi_map(fun(A,B,C) -> A+B+C end, X).The release upgrade instructions, including instructions for
lists2
, are as follows:[{load_module, lists2, soft_purge, soft_purge, []}, {load_module, bar, soft_purge, soft_purge, [lists2]}]
We must state that
bar
is dependent onlists2
so the release handler can loadlists2
before it loadsbar
.The low-level variant of the instructions are:
[{load_object_code, {foo, "1.2", [lists2, bar]}}, point_of_no_return, {load, {lists2, soft_purge, soft_purge}} {load, {bar, soft_purge, soft_purge}}]4.7.1.3 Advanced Functional Module
Suppose now that we want to change the return value from
lists2:assoc/2
from{ok, Val}
to{Key, Val}
. To solve this, we would have to find all modules (process and callbacks) that may calllists2:assoc/2
directly or indirectly, and specify that these modules are dependent onlists2
. In practice, we do not even try to do this, as our code useslists2
extensively. Instead, we do this change in a release which restarts the system.4.7.1.4 Advanced gen_server
This example assumes that we have a
gen_server
process that must be updated because we have introduced a new function, and added a new data field in our internal state. The original module looks as follows:%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(gs1). -vsn(1). -behaviour(gen_server). -export([get_data/0]). -export([init/1, handle_call/3]). -record(state, {data}). get_data() -> gen_server:call(gs1, get_data). init([Data]) -> {ok, #state{data = Data}}. handle_call(get_data, _From, State) -> {reply, {ok, State#state.data}, State}.The new module must translate the old state into the new state. Recall that a record is just syntactic sugar for a tuple:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(gs1). -vsn(2). -behaviour(gen_server). -export([get_data/0, get_time/0]). -export([init/1, handle_call/3]). -export([code_change/3]). -record(state, {data, time}). get_data() -> gen_server:call(gs1, get_data). get_time() -> gen_server:call(gs1, get_time). init([Data]) -> {ok, #state{data = Data, time = erlang:time()}}. handle_call(get_data, _From, State) -> {reply, {ok, State#state.data}, State}; handle_call(get_time, _From, State) -> {reply, {ok, State#state.time}, State}. code_change(1, {state, Data}, _Extra) -> {ok, #state{data = Data, time = erlang:time()}}.The release upgrade instructions look as follows:
[{update, gs1, {advanced, []}, soft_purge, soft_purge, []}]The alternative low-level instructions are:
[{load_object_code, {foo, "1.2", [gs1]}}, point_of_no_return, {suspend, [gs1]}, {load, {gs1, soft_purge, soft_purge}}, {code_change, [{gs1, []}]}, {resume, [gs1]}]If we want to handle soft downgrade as well, the code would look like:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(gs1). -vsn(2). -behaviour(gen_server). -export([get_data/0, get_time/0]). -export([init/1, handle_call/3]). -export([code_change/3]). -record(state, {data, time}). get_data() -> gen_server:call(gs1, get_data). get_time() -> gen_server:call(gs1, get_time). init([Data]) -> {ok, #state{data = Data, time = erlang:time()}}. handle_call(get_data, _From, State) -> {reply, {ok, State#state.data}, State}; handle_call(get_time, _From, State) -> {reply, {ok, State#state.time}, State}. code_change(1, {state, Data}, _Extra) -> {ok, #state{data = Data, time = erlang:time()}}; code_change({down, 1}, #state{data = Data}, _Extra) -> {ok, {state, Data}}.Note that we take care of translating the new state to the old format as well. The corresponding low-level instructions are generated from the same high-level instructions:
[{load_object_code, {foo, "1.2", [gs1]}}, point_of_no_return, {suspend, [gs1]}, {code_change, [{gs1, []}]}, {load, {gs1, soft_purge, soft_purge}}, {resume, [gs1]}]4.7.1.5 Advanced gen_server with Dependencies
This example further assumes that we have another
gen_server
process that uses the new functionality ings1
. Otherwise, it has not changed. This is the same situation as above with the functional moduleslists2
andbar
, but here there are now processes involved.The original module looks as follows:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(gs2). -vsn(1). -behaviour(gen_server). -export([is_operation_ok/1]). -export([init/1, handle_call/3]). is_operation_ok(Op) -> gen_server:call(gs2, {is_operation_ok, Op}). init([Data]) -> {ok, []}. handle_call({is_operation_ok, Op}, _From, State) -> Data = gs1:get_data(), Reply = lists2:assoc(Op, Data), {reply, Reply, State}.In this case, the new module must not translate its state as it has not changed. Therefore, it does not have to specify a
code_change
function.%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(gs2). -vsn(2). -behaviour(gen_server). -export([is_operation_ok/1]). -export([init/1, handle_call/3]). is_operation_ok(Op) -> gen_server:call(gs2, {is_operation_ok, Op}). init([Data]) -> {ok, []}. handle_call({is_operation_ok, Op}, _From, State) -> Data = gs1:get_data(), Time = gs1:get_time(), Reply = complicated_calculation(lists2:assoc(Op, Data), Time), {reply, Reply, State}.The release upgrade instructions are:
[{update, gs1, {advanced, []}, soft_purge, soft_purge, []}, {update, gs2, soft, soft_purge, soft_purge, [gs1]},The low-level alternative instructions are:
[{load_object_code, {foo, "1.2", [gs1, gs2]}}, point_of_no_return, {suspend, [gs1, gs2]}, {load, {gs1, soft_purge, soft_purge}}, {load, {gs2, soft_purge, soft_purge}}, {code_change, [{gs1, []}]}, % No gs2 here! {resume, [gs1, gs2]}]4.7.1.6 Other Worker Processes
All other worker processes in a supervision tree, such as the process types
gen_event
,gen_fsm
, and other processes written withproc_lib
andsys
, are handled in exactly the same way as gen_server. Some more examples of these kind of processes are shown below.4.7.1.7 Simple gen_event
This example shows how an event handler may be updated. We do not make any assumptions about which event manager processes the handler is installed in, it is the responsibility of the release handler to find them. The original module looks as follows:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(ge_h). -vsn(1). -behaviour(gen_event). -export([get_events/1]). -export([init/1, handle_event/2, handle_call/2]). get_events(Mgr) -> gen_event:call(Mgr, ge_h, get_events). init(_) -> {ok, undefined}. handle_event(Event, _LastEvent) -> {ok, Event}. handle_call(get_events, LastEvent) -> [LastEvent].The new module decides to keep the two latest events in a list and must translate the old state into the new state.
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(ge_h). -vsn(2). -behaviour(gen_event). -export([get_events/1]). -export([init/1, handle_event/2, handle_call/2]). -export([code_change/3]). get_events(Mgr) -> gen_event:call(Mgr, ge_h, get_events). init(_) -> {ok, []}. handle_event(Event, []) -> {ok, [Event]}; handle_event(Event, [Event1 | _]) -> {ok, [Event, Event1]}. handle_call(get_events, Events) -> Events. code_change(1, undefined, _Extra) -> {ok, []}; code_change(1, LastEvent, _Extra) -> {ok, [LastEvent]}.The release upgrade instructions are:
[{update, ge_h, {advanced, []}, soft_purge, soft_purge, []}]The low-level alternative instructions are:
[{load_object_code, {foo, "1.2", [ge_h]}}, point_of_no_return, {suspend, [ge_h]}, {load, {ge_h, soft_purge, soft_purge}}, {code_change, [{ge_h, []}]}, {resume, [ge_h]}]
These instructions are identical to those used for the
gen_server
.4.7.1.8 Process Implemented with sys and proc_lib
These processes are changed in the same way as the gen_servers shown above (not surprising since
gen_server
is written using these modules). However, the code change function is defined differently. The original code looks as follows:%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(sp). -vsn(1). -export([start/0, get_data/0]). -export([init/1, system_continue/3, system_terminate/4]). -record(state, {data}). start() -> Pid = proc_lib:spawn_link(test, init, [self()]), register(sp_server, Pid), {ok, Pid}. get_data() -> sp_server ! {self(), get_data}, receive {data, Data} -> Data end. init(Parent) -> process_flag(trap_exit, true), loop(#state{}, Parent). loop(State, Parent) -> receive {system, From, Request} -> sys:handle_system_msg(Request, From, Parent, test, [], State); {'EXIT', Parent, Reason} -> cleanup(State), exit(Reason); {From, get_data} -> From ! {self(), State#state.data}, loop(State, Parent); _Any -> loop(State, Parent) end. cleanup(State) -> ok. %% Here are the sys call back functions system_continue(Parent, _, State) -> loop(State, Parent). system_terminate(Reason, Parent, _, State) -> cleanup(State), exit(Reason).The new code, which takes care of up- and downgrade looks as follows:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(sp). -vsn(2). -export([start/0, get_data/0, set_data/1]). -export([init/1, system_continue/3, system_terminate/4, system_code_change/4]). -record(state, {data, last_pid}). start() -> Pid = proc_lib:spawn_link(test, init, [self()]), register(sp_server, Pid), {ok, Pid}. get_data() -> sp_server ! {self(), get_data}, receive {sp_server, Data} -> Data end. set_data(Data) -> sp_server ! {self(), set_data, Data}. init(Parent) -> process_flag(trap_exit, true), loop(#state{last_pid = no_one}, Parent). loop(State, Parent) -> receive {system, From, Request} -> sys:handle_system_msg(Request, From, Parent, test, [], State); {'EXIT', Parent, Reason} -> cleanup(State), exit(Reason); {From, get_data} -> From ! {sp_server, State#state.data}, loop(State, Parent); {From, set_data, Data} -> loop(State#state{data = Data, last_pid = From}, Parent); _Any -> loop(State, Parent) end. cleanup(State) -> ok. %% Here are the sys call back functions system_continue(Parent, _, State) -> loop(State, Parent). system_terminate(Reason, Parent, _, State) -> cleanup(State), exit(Reason). system_code_change({state, Data}, _Mod, 1, _Extra) -> {ok, #state{data = Data, last_pid = no_one}}; system_code_change(#state{data = Data}, _Mod, {down, 1}, _Extra) -> {ok, {state, Data}}.The release upgrade instructions are:
[{update, sp, static, default, {advanced, []}, soft_purge, soft_purge, []}]The low-level alternative instructions are the same for upgrade and downgrade:
[{load_object_code, {foo, "1.2", [sp]}}, point_of_no_return, {suspend, [sp]}, {load, {sp, soft_purge, soft_purge}}, {code_change, [{sp, []}]}, {resume, [sp]}]4.7.1.9 Supervisor
This example assumes that a new version of an application adds a new process, and deletes one process from a supervisor. The original code looks as follows:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(sup). -vsn(1). -behaviour(supervisor). -export([init/1]). init([]) -> SupFlags = {one_for_one, 4, 3600}, Server = {my_server, {my_server, start_link, []}, permanent, 2000, worker, [my_server]}, GS1 = {gs1, {gs1, start_link, []}, permanent, 2000, worker, [gs1]}, {ok, {SupFlags, [Server, GS1]}}.The new code looks as follows:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(sup). -vsn(2). -behaviour(supervisor). -export([init/1]). init([]) -> SupFlags = {one_for_one, 4, 3600}, GS1 = {gs1, {gs1, start_link, []}, permanent, 2000, worker, [gs1]}, GS2 = {gs2, {gs2, start_link, []}, permanent, 2000, worker, [gs2]}, {ok, {SupFlags, [GS1, GS2]}}.The release upgrade instructions are:
[{update, sup, {advanced, []}, soft_purge, soft_purge, []} {apply, {supervisor, terminate_child, [sup, my_server]}}, {apply, {supervisor, delete_child, [sup, my_server]}}, {apply, {supervisor, restart_child, [sup, gs2]}}]The low-level alternative instructions are:
[{load_object_code, {foo, "1.2", [sup]}}, point_of_no_return, {suspend, [sup]}, {load, {sup, soft_purge, soft_purge}}, {code_change, [{sup, []}]}, {resume, [sup]}, {apply, {supervisor, terminate_child, [sup, my_server]}}, {apply, {supervisor, delete_child, [sup, my_server]}}, {apply, {supervisor, restart_child, [sup, gs2]}}]When a supervisor is updated, it is updated as an advanced code change. In the
code_change
function of the supervisor, the new child specification is installed, but no children are explicitly terminated or started. Therefore, we must terminate and start the children withapply
.4.7.1.10 Complex Dependencies
As already mentioned, sometimes the simplest and safest way to introduce a new release is to terminate parts of the system, load the new code, and restart that part. However, we cannot just kill the individual processes because their supervisors will probably restart them again before we have loaded the new code. Instead, we must explicitly tell the supervisor not to restart the children. We do this by using the
stop
andstart
instructions.This example assumes that we have a supervisor
a
with two childrenb
andc
, whereb
is a worker andc
is a supervisor ford
. We now want to restart all processes except fora
. The upgrade instructions look as follows:[{load_object_code, {foo, "1.2", [b,c,d]}}, point_of_no_return, {stop, [b, c]}, {load, {b, soft_purge, soft_purge}}, {load, {c, soft_purge, soft_purge}}, {load, {d, soft_purge, soft_purge}}, {start, [b, c]}]
We do not need to explicitly terminate
d
, this is done by the supervisorc
.4.7.1.11 New Application
The examples shown so far have dealt with changing an existing application. How do we introduce a new application? We just have to call
application:start_application
and make sure that the application is present in the new boot file that is shipped with each new release. The following example shows how to to introduce the applicationnew_appl
, which has just one module,new_mod
.The release upgrade instructions are:
[{add_application, new_appl}]The low-level alternative instructions (in which the application specification is copied) look as follows:
[{load_object_code, {new_appl, "1.0", [new_mod]}}, point_of_no_return, {load, {new_mod, soft_purge, soft_purge}}, {apply, {application, start_application, [{application, new_appl, [{description, "NEW APPL"}, {vsn, "1.0"}, {modules, [new_mod]}, {registered, []}, {applications, [kernel, foo]}, {env, []}, {mod, {new_mod, start_link, []}}]}, permanent]}}].4.7.1.12 Remove an Application
An application is removed in the same fashion as new applications are introduced. This example assumes that we want to remove the newly introduced
new_appl
:[{remove_application, new_appl}]The low-variant alternative instructions are:
[point_of_no_return, {apply, {application, stop, [new_appl]}}, {remove, {new_mod, soft_purge, soft_purge}}].4.7.2 Update of Port Programs
Each port program is controlled by a Erlang process called the port controller. A port program is updated by the port controller process. It is always done by terminating the old port program, and starting the new one.
4.7.2.1 Port Controller (IG)
When the C Interface Generator (IG) is used, Erlang and C code is generated from a header file. In this way, the Erlang module is connected to the port program. Changes are always done simultaneously for the Erlang and C code . This is automatically taken care of by IG in the code change function of the IG process. Suppose that we want to change the IG generated module
portc_ig
. We must make sure that the code change function is called. The release upgrade instructions will look as follows:[{update, portc_ig, {advanced, []}, soft_purge, soft_purge, []}]Or, the low-level variant:
[{load_object_code, {foo, "1.2", [portc_ig]}}, point_of_no_return, {suspend, [portc_ig]}, {load, {portc_ig, soft_purge, soft_purge}}, {code_change, [{portc_ig, []}]}, {resume, [portc_ig]}]4.7.2.2 Port Controller (Ordinary Process)
This example assumes that we have an ordinary port controller process. This is a port controller which is not written with IG. In this case, we must take care of the termination and restart of the port program ourselves. Also, we may prepare for the possibility of changing the port program without changing the corresponding Erlang code, and the other way round. We use a
gen_server
to implement the port controller. The original module looks as follows:%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(portc). -vsn(1). -behaviour(gen_server). -export([get_data/0]). -export([init/1, handle_call/3, handle_info/2, code_change/3]). -record(state, {port, data}). get_data() -> gen_server:call(portc, get_data). init([]) -> PortProg = code:priv_dir(foo) ++ "/bin/portc", Port = open_port({spawn, PortProg}, [binary, {packet, 2}]) {ok, #state{port = Port}}. handle_call(get_data, _From, State) -> {reply, {ok, State#state.data}, State}. handle_info({Port, Cmd}, State) -> NewState = do_cmd(Cmd, State), {noreply, NewState}. code_change(_, State, change_port_only) -> State#state.port ! close, receive {Port, closed} -> true end, NPortProg = code:priv_dir(foo) ++ "/bin/portc", % get new version NPort = open_port({spawn, NPortProg}, [binary, {packet, 2}]), {ok, State#state{port = NPort}}.To change the port program without changing the Erlang code, we can use the following code:
[point_of_no_return, {suspend, [portc]}, {code_change, [{portc, change_port_only}]}, {resume, [portc]}]In this situation, we use the low-level instructions directly. It is also the first example that uses the
Extra
argument.Suppose now that we wish to change the Erlang code instead. The new Erlang module looks as follows:
%% ``The contents of this file are subject to the Erlang Public License, %% Version 1.0, (the "License"); you may not use this file except in %% compliance with the License. You may obtain a copy of the License at %% http://www.erlang.org/EPL1_0.txt %% %% 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. %% %% The Original Code is Erlang-4.7.3, December, 1998. %% %% The Initial Developer of the Original Code is Ericsson Telecom %% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson %% Telecom AB. All Rights Reserved. %% %% Contributor(s): ______________________________________.'' %% -module(portc). -vsn(2). -behaviour(gen_server). -export([get_data/0]). -export([init/1, handle_call/3, handle_info/2, code_change/3]). -record(state, {port, data}). get_data() -> gen_server:call(portc, get_data). init([]) -> PortProg = code:priv_dir(foo) ++ "/bin/portc", Port = open_port({spawn, PortProg}, [binary, {packet, 2}]) {ok, #state{port = Port}}. handle_call(get_data, _From, State) -> {reply, {ok, State#state.data}, State}. handle_info({Port, Cmd}, State) -> NewState = do_cmd(Cmd, State), {noreply, NewState}. code_change(_, State, change_port_only) -> State#state.port ! close, receive {Port, closed} -> true end, NPortProg = code:priv_dir(foo) ++ "/bin/portc", % get new version NPort = open_port({spawn, NPortProg}, [binary, {packet, 2}]), {ok, State#state{port = NPort}}; code_change(1, State, change_erl_only) -> NState = transform_state(State), {ok, NState}.The relup instructions are:
[{update, portc, {advanced, change_erl_only}, soft_purge, soft_purge, []}]The low-level alternative instructions are:
[{load_object_code, {foo, "1.2", [portc]}}, point_of_no_return, {suspend, [portc]}, {load, {portc, soft_purge, soft_purge}}, {code_change, [{portc, change_erl_only}]}, {resume, [portc]}]