This chapter contains examples of
When a change has been made to a functional module, for example if a new function has been added or a bug has been corrected, simple code replacement is sufficient.
Example:
{"2",
[{"1", [{load_module, m}]}],
[{"1", [{load_module, m}]}]
}.
In a system implemented according to the OTP Design Principles,
all processes, except system processes and special processes,
reside in one of the behaviours
OTP thus provides no support for changing residence modules
except in the case of
A callback module is a functional module, and for code extensions simple code replacement is sufficient.
Example: When adding a function to
{"2",
[{"1", [{load_module, ch3}]}],
[{"1", [{load_module, ch3}]}]
}.
OTP also supports changing the internal state of behaviour
processes, see
In this case, simple code replacement is not sufficient.
The process must explicitly transform its state using the callback
function
Example: Consider the gen_server
The
{"2",
[{"1", [{update, ch3, {advanced, []}}]}],
[{"1", [{update, ch3, {advanced, []}}]}]
}.
The third element of the
-module(ch3).
...
-export([code_change/3]).
...
code_change({down, _Vsn}, {Chs, N}, _Extra) ->
{ok, Chs};
code_change(_Vsn, Chs, _Extra) ->
{ok, {Chs, 0}}.
The first argument is
The version is defined by the module attribute
(The other callback functions of
Assume we extend a module by adding a new interface function, as
in the example in
If we also add a call to this function, say in the module
Thus,
{load_module, Module, DepMods}
{update, Module, {advanced, Extra}, DepMods}
Example: The module
myapp.appup:
{"2",
[{"1", [{load_module, m1, [ch3]}]}],
[{"1", [{load_module, m1, [ch3]}]}]
}.
ch_app.appup:
{"2",
[{"1", [{load_module, ch3}]}],
[{"1", [{load_module, ch3}]}]
}.
If
{"2",
[{"1",
[{load_module, ch3},
{load_module, m1, [ch3]}]}],
[{"1",
[{load_module, ch3},
{load_module, m1, [ch3]}]}]
}.
Note that it is
In this case, simple code replacement is not sufficient. When a new version of a residence module for a special process is loaded, the process must make a fully qualified call to its loop function to switch to the new code. Thus synchronized code replacement must be used.
The name(s) of the user-defined residence module(s) must be
listed in the
Example. Consider the example
{ch4, {ch4, start_link, []},
permanent, brutal_kill, worker, [ch4]}
If
{"2",
[{"1", [{update, ch4, {advanced, []}}]}],
[{"1", [{update, ch4, {advanced, []}}]}]
}.
The
-module(ch4).
...
-export([system_code_change/4]).
...
system_code_change(Chs, _Module, _OldVsn, _Extra) ->
{ok, Chs}.
The first argument is the internal state
The second argument is the name of the module (
The third argument is
In this case, all arguments but the first are ignored and
the function simply returns the internal state again. This is
enough if the code only has been extended. If we had wanted to
change the internal state (similar to the example in
The supervisor behaviour supports changing the internal state, i.e. changing restart strategy and maximum restart intensity properties, as well as changing existing child specifications.
Adding and deleting child processes are also possible, but not
handled automatically. Instructions must be given by in
the
Since the supervisor should change its internal state,
synchronized code replacement is required. However,
a special
The new version of the callback module must be loaded first
both in the case of upgrade and downgrade. Then the new return
value of
The following
{update, Module, supervisor}
Example: Assume we want to change the restart strategy of
-module(ch_sup).
...
init(_Args) ->
{ok, {#{strategy => one_for_all, ...}, ...}}.
The file
{"2",
[{"1", [{update, ch_sup, supervisor}]}],
[{"1", [{update, ch_sup, supervisor}]}]
}.
The instruction, and thus the
{"2",
[{"1", [{update, ch_sup, supervisor}]}],
[{"1", [{update, ch_sup, supervisor}]}]
}.
The changes do not affect existing child processes. For example, changing the start function only specifies how the child process should be restarted, if needed later on.
Note that the id of the child specification cannot be changed.
Note also that changing the
As stated above, changing child specifications does not affect
existing child processes. New child specifications are
automatically added, but not deleted. Also, child processes are
not automatically started or terminated. Instead, this must be
done explicitly using
Example: Assume we want to add a new child process
{"2",
[{"1",
[{update, ch_sup, supervisor},
{apply, {supervisor, restart_child, [ch_sup, m1]}}
]}],
[{"1",
[{apply, {supervisor, terminate_child, [ch_sup, m1]}},
{apply, {supervisor, delete_child, [ch_sup, m1]}},
{update, ch_sup, supervisor}
]}]
}.
Note that the order of the instructions is important.
Note also that the supervisor must be registered as
If the module
{"2",
[{"1",
[{add_module, m1},
{update, ch_sup, supervisor},
{apply, {supervisor, restart_child, [ch_sup, m1]}}
]}],
[{"1",
[{apply, {supervisor, terminate_child, [ch_sup, m1]}},
{apply, {supervisor, delete_child, [ch_sup, m1]}},
{update, ch_sup, supervisor},
{delete_module, m1}
]}]
}.
Note again that the order of the instructions is important.
When upgrading,
Example: A new functional module
{"2",
[{"1", [{add_module, m}]}],
[{"1", [{delete_module, m}]}]
In a system structured according to the OTP design principles,
any process would be a child process belonging to a supervisor,
see
When adding or removing an application, no
Restarting an application is useful when a change is too complicated to be made without restarting the processes, for example if the supervisor hierarchy has been restructured.
Example: When adding a new child
{"2",
[{"1", [{restart_application, ch_app}]}],
[{"1", [{restart_application, ch_app}]}]
}.
When installing a release, the application specifications are
automatically updated before evaluating the
{"2", [{"1", []}], [{"1", []}] }.
Changing an application configuration by updating the
Alternatively, application configuration parameters can be
added or updated in
The release handling instructions for adding, removing and
restarting applications apply to primary applications only.
There are no corresponding instructions for included
applications. However, since an included application is really a
supervision tree with a topmost supervisor, started as a child
process to a supervisor in the including application, a
Example: Assume we have a release containing an application
In a new version of the release, our example application
1) Edit the code for
init(...) ->
{ok, {...supervisor flags...,
[...,
{ch_sup, {ch_sup,start_link,[]},
permanent,infinity,supervisor,[ch_sup]},
...]}}.
2) Edit the
{application, prim_app,
[...,
{vsn, "2"},
...,
{included_applications, [ch_app]},
...
]}.
3) Create a new
{release,
...,
[...,
{prim_app, "2"},
{ch_app, "1"}]}.
4a) One way to start the included application is to restart
the entire
However, if we did this and then generated a
Instead, a correct
{"B",
[{"A",
[],
[{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
{load_object_code,{prim_app,"2",[prim_app,prim_sup]}},
point_of_no_return,
{apply,{application,stop,[prim_app]}},
{remove,{prim_app,brutal_purge,brutal_purge}},
{remove,{prim_sup,brutal_purge,brutal_purge}},
{purge,[prim_app,prim_sup]},
{load,{prim_app,brutal_purge,brutal_purge}},
{load,{prim_sup,brutal_purge,brutal_purge}},
{load,{ch_sup,brutal_purge,brutal_purge}},
{load,{ch3,brutal_purge,brutal_purge}},
{apply,{application,load,[ch_app]}},
{apply,{application,start,[prim_app,permanent]}}]}],
[{"A",
[],
[{load_object_code,{prim_app,"1",[prim_app,prim_sup]}},
point_of_no_return,
{apply,{application,stop,[prim_app]}},
{apply,{application,unload,[ch_app]}},
{remove,{ch_sup,brutal_purge,brutal_purge}},
{remove,{ch3,brutal_purge,brutal_purge}},
{purge,[ch_sup,ch3]},
{remove,{prim_app,brutal_purge,brutal_purge}},
{remove,{prim_sup,brutal_purge,brutal_purge}},
{purge,[prim_app,prim_sup]},
{load,{prim_app,brutal_purge,brutal_purge}},
{load,{prim_sup,brutal_purge,brutal_purge}},
{apply,{application,start,[prim_app,permanent]}}]}]
}.
4b) Another way to start the included application (or stop it
in the case of downgrade) is by combining instructions for
adding and removing child processes to/from
Again, the
{"B",
[{"A",
[],
[{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
{load_object_code,{prim_app,"2",[prim_sup]}},
point_of_no_return,
{load,{ch_sup,brutal_purge,brutal_purge}},
{load,{ch3,brutal_purge,brutal_purge}},
{apply,{application,load,[ch_app]}},
{suspend,[prim_sup]},
{load,{prim_sup,brutal_purge,brutal_purge}},
{code_change,up,[{prim_sup,[]}]},
{resume,[prim_sup]},
{apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
[{"A",
[],
[{load_object_code,{prim_app,"1",[prim_sup]}},
point_of_no_return,
{apply,{supervisor,terminate_child,[prim_sup,ch_sup]}},
{apply,{supervisor,delete_child,[prim_sup,ch_sup]}},
{suspend,[prim_sup]},
{load,{prim_sup,brutal_purge,brutal_purge}},
{code_change,down,[{prim_sup,[]}]},
{resume,[prim_sup]},
{remove,{ch_sup,brutal_purge,brutal_purge}},
{remove,{ch3,brutal_purge,brutal_purge}},
{purge,[ch_sup,ch3]},
{apply,{application,unload,[ch_app]}}]}]
}.
Changing code for a program written in another programming language than Erlang, for example a port program, is very application dependent and OTP provides no special support for it.
Example, changing code for a port program: Assume that
the Erlang process controlling the port is a gen_server
init(...) ->
...,
PortPrg = filename:join(code:priv_dir(App), "portc"),
Port = open_port({spawn,PortPrg}, [...]),
...,
{ok, #state{port=Port, ...}}.
If the port program should be updated, we can extend the code for
the gen_server with a
code_change(_OldVsn, State, port) ->
State#state.port ! close,
receive
{Port,close} ->
true
end,
PortPrg = filename:join(code:priv_dir(App), "portc"),
Port = open_port({spawn,PortPrg}, [...]),
{ok, #state{port=Port, ...}}.
Update the application version number in the
["2",
[{"1", [{update, portc, {advanced,port}}]}],
[{"1", [{update, portc, {advanced,port}}]}]
].
Make sure the
1> systools:make_tar("my_release", [{dirs,[priv]}]). ...
There are two upgrade instructions that will restart the emulator:
If an emulator restart is necessary and no upgrade instructions
are needed, i.e. if the restart itself is enough for the
upgraded applications to start running the new versions, a very
simple
{"B",
[{"A",
[],
[restart_emulator]}],
[{"A",
[],
[restart_emulator]}]
}.
In this case, the release handler framework with automatic
packing and unpacking of release packages, automatic path
updates etc. can be used without having to specify
From OTP R15, an emulator upgrade is performed by restarting
the emulator with new versions of the core applications
(