[Erlang Systems]

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:

  1. move all applications from the node to be changed to other nodes
  2. take down the node
  3. do the change
  4. 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:

  1. 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.
  2. The release packet must be transferred to the target machine. This can be done with a method like ftp.
  3. 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.
  4. 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 where release_handler:unpack_release/1 looks for the packages, and where the release handler stores its files. Each packet is a compressed tar file. The files in the tar file are named relative to the $OTP_ROOT directory. For example, if a new version (say 1.3) of the application snmp is contained in the release packet, the files in the tar file should be named lib/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:

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.

Vsn is the system version string, and EVsn 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 and AVsn are strings, App and ATytpe are atoms. ReleaseName is a string given in the call to release_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 and ToVsn 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 by release_handler:install_release/1 and release_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 with release_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 from FromVsn "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 call install_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:

  1. 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.
  2. 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 a fun, 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.

The following sequence of events may occur:

  1. a is suspended.
  2. the release handler then tries to suspend b, but some microsecond before this happens, b tries to communicate with a which is now suspended
  3. If b hangs in its call to a, the suspension of b fails and only a is updated.
  4. If b notices that a does not answer and is able to deal with it, then b receives the suspend message and is suspended. Then both modules are updated and resumed.
  5. When a resumes, there is a message waiting from b. This message may be of an old format which a 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 the relup 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 a relup 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:

  1. Suspend the processes running the module
  2. Load the new code
  3. 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.
  4. 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:

  1. Suspend the processes running the module
  2. 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.
  3. Load the new code
  4. Tell the processes to switch code
  5. 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 a relup file with low-level instructions.

4.6.1 High-level Instructions

The high-level instructions are defined as follows:

4.6.2 Low-level instructions

The low-level instructions are defined as follows:

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 of lists2. 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 function complicated_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]}]
        

Note!

We must state that bar is dependent on lists2 so the release handler can load lists2 before it loads bar.

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 call lists2:assoc/2 directly or indirectly, and specify that these modules are dependent on lists2. In practice, we do not even try to do this, as our code uses lists2 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 in gs1. Otherwise, it has not changed. This is the same situation as above with the functional modules lists2 and bar, 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 with proc_lib and sys, 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]}]
        

Note!

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 with apply.

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 and start instructions.

This example assumes that we have a supervisor a with two children b and c, where b is a worker and c is a supervisor for d. We now want to restart all processes except for a. 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]}]
        

Note!

We do not need to explicitly terminate d, this is done by the supervisor c.

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 application new_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]}]
        

Copyright © 1991-98 Ericsson Telecom AB