aboutsummaryrefslogtreecommitdiffstats
path: root/lib/sasl/src/release_handler_1.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/sasl/src/release_handler_1.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/sasl/src/release_handler_1.erl')
-rw-r--r--lib/sasl/src/release_handler_1.erl647
1 files changed, 647 insertions, 0 deletions
diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl
new file mode 100644
index 0000000000..e3e3caba99
--- /dev/null
+++ b/lib/sasl/src/release_handler_1.erl
@@ -0,0 +1,647 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(release_handler_1).
+
+%% External exports
+-export([eval_script/3, eval_script/4, check_script/2]).
+-export([get_vsn/1]). %% exported because used in a test case
+
+-record(eval_state, {bins = [], stopped = [], suspended = [], apps = [],
+ libdirs, unpurged = [], vsns = [], newlibs = [],
+ opts = []}).
+%%-----------------------------------------------------------------
+%% bins = [{Mod, Binary, FileName}]
+%% stopped = [{Mod, [pid()]}] - list of stopped pids for each module
+%% suspended = [{Mod, [pid()]}] - list of suspended pids for each module
+%% apps = [app_spec()] - list of all apps in the new release
+%% libdirs = [{Lib, LibVsn, LibDir}] - Maps Lib to Vsn and Directory
+%% unpurged = [{Mod, soft_purge | brutal_purge}]
+%% vsns = [{Mod, OldVsn, NewVsn}] - remember the old vsn of a mod
+%% before it is removed/a new vsn is loaded; the new vsn
+%% is kept in case of a downgrade, where the code_change
+%% function receives the vsn of the module to downgrade
+%% *to*.
+%% newlibs = [{Lib, Dir}] - list of all new libs; used to change
+%% the code path
+%% opts = [{Tag, Value}] - list of options
+%%-----------------------------------------------------------------
+
+
+%%%-----------------------------------------------------------------
+%%% This is a low-level release handler.
+%%%-----------------------------------------------------------------
+check_script(Script, LibDirs) ->
+ case catch check_old_processes(Script) of
+ ok ->
+ {Before, _After} = split_instructions(Script),
+ case catch lists:foldl(fun(Instruction, EvalState1) ->
+ eval(Instruction, EvalState1)
+ end,
+ #eval_state{libdirs = LibDirs},
+ Before) of
+ EvalState2 when is_record(EvalState2, eval_state) -> ok;
+ {error, Error} -> {error, Error};
+ Other -> {error, Other}
+ end;
+ {error, Mod} ->
+ {error, {old_processes, Mod}}
+ end.
+
+eval_script(Script, Apps, LibDirs) ->
+ eval_script(Script, Apps, LibDirs, []).
+
+eval_script(Script, Apps, LibDirs, Opts) ->
+ case catch check_old_processes(Script) of
+ ok ->
+ {Before, After} = split_instructions(Script),
+ case catch lists:foldl(fun(Instruction, EvalState1) ->
+ eval(Instruction, EvalState1)
+ end,
+ #eval_state{apps = Apps,
+ libdirs = LibDirs,
+ opts = Opts},
+ Before) of
+ EvalState2 when is_record(EvalState2, eval_state) ->
+ case catch lists:foldl(fun(Instruction, EvalState3) ->
+ eval(Instruction, EvalState3)
+ end,
+ EvalState2,
+ After) of
+ EvalState4 when is_record(EvalState4, eval_state) ->
+ {ok, EvalState4#eval_state.unpurged};
+ restart_new_emulator ->
+ restart_new_emulator;
+ Error ->
+ {'EXIT', Error}
+ end;
+ {error, Error} -> {error, Error};
+ Other -> {error, Other}
+ end;
+ {error, Mod} ->
+ {error, {old_processes, Mod}}
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+%%%-----------------------------------------------------------------
+split_instructions(Script) ->
+ split_instructions(Script, []).
+split_instructions([point_of_no_return | T], Before) ->
+ {lists:reverse(Before), [point_of_no_return | T]};
+split_instructions([H | T], Before) ->
+ split_instructions(T, [H | Before]);
+split_instructions([], Before) ->
+ {[], lists:reverse(Before)}.
+
+%%-----------------------------------------------------------------
+%% Func: check_old_processes/1
+%% Args: Script = [instruction()]
+%% Purpose: Check if there is any process that runs an old version
+%% of a module that should be soft_purged, (i.e. not purged
+%% at all if there is any such process). Returns {error, Mod}
+%% if so, ok otherwise.
+%% Returns: ok | {error, Mod}
+%% Mod = atom()
+%%-----------------------------------------------------------------
+check_old_processes(Script) ->
+ lists:foreach(fun({load, {Mod, soft_purge, _PostPurgeMethod}}) ->
+ check_old_code(Mod);
+ ({remove, {Mod, soft_purge, _PostPurgeMethod}}) ->
+ check_old_code(Mod);
+ (_) -> ok
+ end,
+ Script).
+
+check_old_code(Mod) ->
+ lists:foreach(fun(Pid) ->
+ case erlang:check_process_code(Pid, Mod) of
+ false -> ok;
+ true -> throw({error, Mod})
+ end
+ end,
+ erlang:processes()).
+
+%%-----------------------------------------------------------------
+%% An unpurged module is a module for which there exist an old
+%% version of the code. This should only be the case if there are
+%% processes running the old version of the code.
+%%
+%% This functions evaluates each instruction. Note that the
+%% instructions here are low-level instructions. e.g. lelle's
+%% old synchronized_change would be translated to
+%% {load_object_code, Modules},
+%% {suspend, Modules}, [{load, Module}],
+%% {resume, Modules}, {purge, Modules}
+%% Or, for example, if we want to do advanced external code change
+%% on two modules that depend on each other, by killing them and
+%% then restaring them, we could do:
+%% {load_object_code, [Mod1, Mod2]},
+%% % delete old version
+%% {remove, {Mod1, brutal_purge}}, {remove, {Mod2, brutal_purge}},
+%% % now, some procs migth be running prev current (now old) version
+%% % kill them, and load new version
+%% {load, {Mod1, brutal_purge}}, {load, {Mod2, brutal_purge}}
+%% % now, there is one version of the code (new, current)
+%%
+%% NOTE: All load_object_code must be first in the script,
+%% a point_of_no_return must be present (if load_object_code
+%% is present).
+%%
+%% {load_object_code, {Lib, LibVsn, [Mod]}
+%% read the files as binarys. do not make code out of them
+%% {load, {Module, PrePurgeMethod, PostPurgeMethod}}
+%% Module must have been load_object_code:ed. make code out of it
+%% old procs && soft_purge => no new release
+%% old procs && brutal_purge => old procs killed
+%% The new old code will be gc:ed later on, if PostPurgeMethod =
+%% soft_purge. If it is brutal_purge, the code is purged when
+%% the release is made permanent.
+%% {remove, {Module, PrePurgeMethod, PostPurgeMethod}}
+%% make current version old. no current left.
+%% old procs && soft_purge => no new release
+%% old procs && brutal_purge => old procs killed
+%% The new old code will be gc:ed later on, if PostPurgeMethod =
+%% soft_purge. If it is brutal_purge, the code is purged when
+%% the release is made permanent.
+%% {purge, Modules}
+%% kill all procs running old code, delete old code
+%% {suspend, [Module | {Module, Timeout}]}
+%% If a process doesn't repsond - never mind. It will be killed
+%% later on (if a purge is performed).
+%% Hmm, we must do something smart here... we should probably kill it,
+%% but we cant, because its supervisor will restart it directly! Maybe
+%% we should keep a list of those, call supervisor:terminate_child()
+%% when all others are suspended, and call sup:restart_child() when the
+%% others are resumed.
+%% {code_change, [{Module, Extra}]}
+%% {code_change, Mode, [{Module, Extra}]} Mode = up | down
+%% Send code_change only to suspended procs running this code
+%% {resume, [Module]}
+%% resume all previously suspended processes
+%% {stop, [Module]}
+%% stop all procs running this code
+%% {start, [Module]}
+%% starts the procs that were previously stopped for this code.
+%% Note that this will start processes in exactly the same place
+%% in the suptree where there were procs previously.
+%% {sync_nodes, Id, [Node]}
+%% {sync_nodes, Id, {M, F, A}}
+%% Synchronizes with the Nodes (or apply(M,F,A) == Nodes). All Nodes
+%% must also exectue the same line. Waits for all these nodes to get
+%% to this line.
+%% point_of_no_return
+%% restart_new_emulator
+%% {stop_application, Appl} - Impl with apply
+%% {unload_application, Appl} - Impl with {remove..}
+%% {load_application, Appl} - Impl with {load..}
+%% {start_application, Appl} - Impl with apply
+%%-----------------------------------------------------------------
+eval({load_object_code, {Lib, LibVsn, Modules}}, EvalState) ->
+ case lists:keysearch(Lib, 1, EvalState#eval_state.libdirs) of
+ {value, {Lib, LibVsn, LibDir}} ->
+ Ebin = filename:join(LibDir, "ebin"),
+ Ext = code:objfile_extension(),
+ {NewBins, NewVsns} =
+ lists:foldl(fun(Mod, {Bins, Vsns}) ->
+ File = lists:concat([Mod, Ext]),
+ FName = filename:join(Ebin, File),
+ case erl_prim_loader:get_file(FName) of
+ {ok, Bin, FName2} ->
+ NVsns = add_new_vsn(Mod, FName2, Vsns),
+ {[{Mod, Bin, FName2} | Bins],NVsns};
+ error ->
+ throw({error, {no_such_file,FName}})
+ end
+ end,
+ {EvalState#eval_state.bins,
+ EvalState#eval_state.vsns},
+ Modules),
+ NewLibs = [{Lib, Ebin} | EvalState#eval_state.newlibs],
+ EvalState#eval_state{bins = NewBins,
+ newlibs = NewLibs,
+ vsns = NewVsns};
+ {value, {Lib, LibVsn2, _LibDir}} ->
+ throw({error, {bad_lib_vsn, Lib, LibVsn2}})
+ end;
+eval(point_of_no_return, EvalState) ->
+ Libs = case get_opt(update_paths, EvalState, false) of
+ false ->
+ EvalState#eval_state.newlibs; % [{Lib, Path}]
+ true ->
+ lists:map(fun({Lib, _LibVsn, LibDir}) ->
+ Ebin= filename:join(LibDir,"ebin"),
+ {Lib, Ebin}
+ end,
+ EvalState#eval_state.libdirs)
+ end,
+ lists:foreach(fun({Lib, Path}) -> code:replace_path(Lib, Path) end,
+ Libs),
+ EvalState;
+eval({load, {Mod, _PrePurgeMethod, PostPurgeMethod}}, EvalState) ->
+ Bins = EvalState#eval_state.bins,
+ {value, {_Mod, Bin, File}} = lists:keysearch(Mod, 1, Bins),
+ % load_binary kills all procs running old code
+ % if soft_purge, we know that there are no such procs now
+ Vsns = EvalState#eval_state.vsns,
+ NewVsns = add_old_vsn(Mod, Vsns),
+ code:load_binary(Mod, File, Bin),
+ % Now, the prev current is old. There might be procs
+ % running it. Find them.
+ Unpurged = do_soft_purge(Mod,PostPurgeMethod,EvalState#eval_state.unpurged),
+ EvalState#eval_state{bins = lists:keydelete(Mod, 1, Bins),
+ unpurged = Unpurged,
+ vsns = NewVsns};
+eval({remove, {Mod, _PrePurgeMethod, PostPurgeMethod}}, EvalState) ->
+ % purge kills all procs running old code
+ % if soft_purge, we know that there are no such procs now
+ Vsns = EvalState#eval_state.vsns,
+ NewVsns = add_old_vsn(Mod, Vsns),
+ code:purge(Mod),
+ code:delete(Mod),
+ % Now, the prev current is old. There might be procs
+ % running it. Find them.
+ Unpurged =
+ case code:soft_purge(Mod) of
+ true -> EvalState#eval_state.unpurged;
+ false -> [{Mod, PostPurgeMethod} | EvalState#eval_state.unpurged]
+ end,
+%% Bins = EvalState#eval_state.bins,
+%% EvalState#eval_state{bins = lists:keydelete(Mod, 1, Bins),
+ EvalState#eval_state{unpurged = Unpurged, vsns = NewVsns};
+eval({purge, Modules}, EvalState) ->
+ % Now, if there are any processes still executing old code, OR
+ % if some new processes started after suspend but before load,
+ % these are killed.
+ lists:foreach(fun(Mod) -> code:purge(Mod) end, Modules),
+ EvalState;
+eval({suspend, Modules}, EvalState) ->
+ Procs = get_supervised_procs(),
+ NewSuspended =
+ lists:foldl(fun(ModSpec, Suspended) ->
+ {Module, Def} = case ModSpec of
+ {Mod, ModTimeout} ->
+ {Mod, ModTimeout};
+ Mod ->
+ {Mod, default}
+ end,
+ Timeout = get_opt(suspend_timeout, EvalState, Def),
+ Pids = suspend(Module, Procs, Timeout),
+ [{Module, Pids} | Suspended]
+ end,
+ EvalState#eval_state.suspended,
+ Modules),
+ EvalState#eval_state{suspended = NewSuspended};
+eval({resume, Modules}, EvalState) ->
+ NewSuspended =
+ lists:foldl(fun(Mod, Suspended) ->
+ lists:filter(fun({Mod2, Pids}) when Mod2 == Mod ->
+ resume(Pids),
+ false;
+ (_) ->
+ true
+ end,
+ Suspended)
+ end,
+ EvalState#eval_state.suspended,
+ Modules),
+ EvalState#eval_state{suspended = NewSuspended};
+eval({code_change, Modules}, EvalState) ->
+ eval({code_change, up, Modules}, EvalState);
+eval({code_change, Mode, Modules}, EvalState) ->
+ Suspended = EvalState#eval_state.suspended,
+ Vsns = EvalState#eval_state.vsns,
+ Timeout = get_opt(code_change_timeout, EvalState, default),
+ lists:foreach(fun({Mod, Extra}) ->
+ Vsn =
+ case lists:keysearch(Mod, 1, Vsns) of
+ {value, {Mod, OldVsn, _NewVsn}}
+ when Mode == up -> OldVsn;
+ {value, {Mod, _OldVsn, NewVsn}}
+ when Mode == down -> {down, NewVsn};
+ _ when Mode == up -> undefined;
+ _ -> {down, undefined}
+ end,
+ case lists:keysearch(Mod, 1, Suspended) of
+ {value, {_Mod, Pids}} ->
+ change_code(Pids, Mod, Vsn, Extra, Timeout);
+ _ -> ok
+ end
+ end,
+ Modules),
+ EvalState;
+eval({stop, Modules}, EvalState) ->
+ Procs = get_supervised_procs(),
+ NewStopped =
+ lists:foldl(fun(Mod, Stopped) ->
+ Procs2 = stop(Mod, Procs),
+ [{Mod, Procs2} | Stopped]
+ end,
+ EvalState#eval_state.stopped,
+ Modules),
+ EvalState#eval_state{stopped = NewStopped};
+eval({start, Modules}, EvalState) ->
+ NewStopped =
+ lists:foldl(fun(Mod, Stopped) ->
+ lists:filter(fun({Mod2, Procs}) when Mod2 == Mod ->
+ start(Procs),
+ false;
+ (_) ->
+ true
+ end,
+ Stopped)
+ end,
+ EvalState#eval_state.stopped,
+ Modules),
+ EvalState#eval_state{stopped = NewStopped};
+eval({sync_nodes, Id, {M, F, A}}, EvalState) ->
+ sync_nodes(Id, apply(M, F, A)),
+ EvalState;
+eval({sync_nodes, Id, Nodes}, EvalState) ->
+ sync_nodes(Id, Nodes),
+ EvalState;
+eval({apply, {M, F, A}}, EvalState) ->
+ apply(M, F, A),
+ EvalState;
+eval(restart_new_emulator, _EvalState) ->
+ throw(restart_new_emulator).
+
+get_opt(Tag, EvalState, Default) ->
+ case lists:keysearch(Tag, 1, EvalState#eval_state.opts) of
+ {value, {_Tag, Value}} -> Value;
+ false -> Default
+ end.
+
+%%-----------------------------------------------------------------
+%% This is a first approximation. Unfortunately, we might end up
+%% with the situation that after this suspendation, some new
+%% processes start *before* we have loaded the new code, and these
+%% will execute the old code. These processes could be terminated
+%% later on (if the prev current version is purged). The same
+%% goes for processes that didn't respond to the suspend message.
+%%-----------------------------------------------------------------
+suspend(Mod, Procs, Timeout) ->
+ lists:zf(fun({_Sup, _Name, Pid, Mods}) ->
+ case lists:member(Mod, Mods) of
+ true ->
+ case catch sys_suspend(Pid, Timeout) of
+ ok -> {true, Pid};
+ _ ->
+ % If the proc hangs, make sure to
+ % resume it when it gets suspended!
+ catch sys:resume(Pid),
+ false
+ end;
+ false ->
+ false
+ end
+ end,
+ Procs).
+
+sys_suspend(Pid, default) ->
+ sys:suspend(Pid);
+sys_suspend(Pid, Timeout) ->
+ sys:suspend(Pid, Timeout).
+
+resume(Pids) ->
+ lists:foreach(fun(Pid) -> catch sys:resume(Pid) end, Pids).
+
+change_code(Pids, Mod, Vsn, Extra, Timeout) ->
+ Fun = fun(Pid) ->
+ case Timeout of
+ default ->
+ ok = sys:change_code(Pid, Mod, Vsn, Extra);
+ _Else ->
+ ok = sys:change_code(Pid, Mod, Vsn, Extra, Timeout)
+ end
+ end,
+ lists:foreach(Fun, Pids).
+
+stop(Mod, Procs) ->
+ lists:zf(fun({undefined, _Name, _Pid, _Mods}) ->
+ false;
+ ({Sup, Name, _Pid, Mods}) ->
+ case lists:member(Mod, Mods) of
+ true ->
+ case catch supervisor:terminate_child(
+ Sup, Name) of
+ ok -> {true, {Sup, Name}};
+ _ -> false
+ end;
+ false -> false
+ end
+ end,
+ Procs).
+
+start(Procs) ->
+ lists:foreach(fun({Sup, Name}) ->
+ catch supervisor:restart_child(Sup, Name)
+ end,
+ Procs).
+
+%%-----------------------------------------------------------------
+%% Func: get_supervised_procs/0
+%% Purpose: This is the magic function. It finds all process in
+%% the system and which modules they execute as a call_back or
+%% process module.
+%% This is achieved by asking the main supervisor for the
+%% applications for all children and their modules
+%% (recursively).
+%% NOTE: If a supervisor is suspended, it isn't possible to call
+%% which_children. Code change on a supervisor should be
+%% done in another way; the only code in a supervisor is
+%% code for starting children. Therefore, to change a
+%% supervisor module, we should load the new version, and then
+%% delete the old. Then we should perform the start changes
+%% manually, by adding/deleting children.
+%% Returns: [{SuperPid, ChildName, ChildPid, Mods}]
+%%-----------------------------------------------------------------
+%% OTP-3452. For each application the first item contains the pid
+%% of the top supervisor, and the name of the supervisor call-back module.
+%%-----------------------------------------------------------------
+
+get_supervised_procs() ->
+ lists:foldl(
+ fun(Application, Procs) ->
+ case application_controller:get_master(Application) of
+ Pid when is_pid(Pid) ->
+ {Root, _AppMod} = application_master:get_child(Pid),
+ case get_supervisor_module(Root) of
+ {ok, SupMod} ->
+ get_procs(supervisor:which_children(Root),
+ Root) ++
+ [{undefined, undefined, Root, [SupMod]} |
+ Procs];
+ {error, _} ->
+ error_logger:error_msg("release_handler: "
+ "cannot find top "
+ "supervisor for "
+ "application ~w~n",
+ [Application]),
+ get_procs(supervisor:which_children(Root),
+ Root) ++ Procs
+ end;
+ _ -> Procs
+ end
+ end,
+ [],
+ lists:map(fun({Application, _Name, _Vsn}) ->
+ Application
+ end,
+ application:which_applications())).
+
+get_procs([{Name, Pid, worker, dynamic} | T], Sup) when is_pid(Pid) ->
+ Mods = get_dynamic_mods(Pid),
+ [{Sup, Name, Pid, Mods} | get_procs(T, Sup)];
+get_procs([{Name, Pid, worker, Mods} | T], Sup) when is_pid(Pid), is_list(Mods) ->
+ [{Sup, Name, Pid, Mods} | get_procs(T, Sup)];
+get_procs([{Name, Pid, supervisor, Mods} | T], Sup) when is_pid(Pid) ->
+ [{Sup, Name, Pid, Mods} | get_procs(T, Sup)] ++
+ get_procs(supervisor:which_children(Pid), Pid);
+get_procs([_H | T], Sup) ->
+ get_procs(T, Sup);
+get_procs(_, _Sup) ->
+ [].
+
+get_dynamic_mods(Pid) ->
+ {ok,Res} = gen:call(Pid, self(), get_modules),
+ Res.
+
+%% XXXX
+%% Note: The following is a terrible hack done in order to resolve the
+%% problem stated in ticket OTP-3452.
+
+%% XXXX NOTE WELL: This record is from supervisor.erl. Also the record
+%% name is really `state'.
+-record(supervisor_state, {name,
+ strategy,
+ children = [],
+ dynamics = [],
+ intensity,
+ period,
+ restarts = [],
+ module,
+ args}).
+
+%% Return the name of the call-back module that implements the
+%% (top) supervisor SupPid.
+%% Returns: {ok, Module} | {error,undefined}
+%%
+get_supervisor_module(SupPid) ->
+ case catch get_supervisor_module1(SupPid) of
+ {ok, Module} when is_atom(Module) ->
+ {ok, Module};
+ _Other ->
+ io:format("~w: reason: ~w~n", [SupPid, _Other]),
+ {error, undefined}
+ end.
+
+get_supervisor_module1(SupPid) ->
+ {status, _Pid, {module, _Mod},
+ [_PDict, _SysState, _Parent, _Dbg, Misc]} = sys:get_status(SupPid),
+ [_Name, State, _Type, _Time] = Misc,
+ %% Cannot use #supervisor_state{module = Module} = State.
+ {ok, element(#supervisor_state.module, State)}.
+
+%%-----------------------------------------------------------------
+%% Func: do_soft_purge/3
+%% Args: Mod = atom()
+%% PostPurgeMethod = soft_purge | brutal_purge
+%% Unpurged = [{Mod, PostPurgeMethod}]
+%% Purpose: Check if there are any processes left running this code.
+%% If so, make sure Mod is a member in the returned list.
+%% Otherwise, make sure Mod isn't a member in the returned
+%% list.
+%% Returns: An updated list of unpurged modules.
+%%-----------------------------------------------------------------
+do_soft_purge(Mod, PostPurgeMethod, Unpurged) ->
+ IsNoOldProcsLeft = code:soft_purge(Mod),
+ case lists:keymember(Mod, 1, Unpurged) of
+ true when IsNoOldProcsLeft == true -> lists:keydelete(Mod, 1, Unpurged);
+ true -> Unpurged;
+ false when IsNoOldProcsLeft == true -> Unpurged;
+ false -> [{Mod, PostPurgeMethod} | Unpurged]
+ end.
+
+%%-----------------------------------------------------------------
+%% Func: sync_nodes/2
+%% Args: Id = term()
+%% Nodes = [atom()]
+%% Purpose: Synchronizes with all nodes.
+%%-----------------------------------------------------------------
+sync_nodes(Id, Nodes) ->
+ NNodes = lists:delete(node(), Nodes),
+ lists:foreach(fun(Node) ->
+ {release_handler, Node} ! {sync_nodes, Id, node()}
+ end,
+ NNodes),
+ lists:foreach(fun(Node) ->
+ receive
+ {sync_nodes, Id, Node} ->
+ ok;
+ {nodedown, Node} ->
+ throw({sync_error, {nodedown, Node}})
+ end
+ end,
+ NNodes).
+
+add_old_vsn(Mod, Vsns) ->
+ case lists:keysearch(Mod, 1, Vsns) of
+ {value, {Mod, undefined, NewVsn}} ->
+ OldVsn = get_vsn(code:which(Mod)),
+ lists:keyreplace(Mod, 1, Vsns, {Mod, OldVsn, NewVsn});
+ {value, {Mod, _OldVsn, _NewVsn}} ->
+ Vsns;
+ false ->
+ OldVsn = get_vsn(code:which(Mod)),
+ [{Mod, OldVsn, undefined} | Vsns]
+ end.
+
+add_new_vsn(Mod, File, Vsns) ->
+ NewVsn = get_vsn(File),
+ case lists:keysearch(Mod, 1, Vsns) of
+ {value, {Mod, OldVsn, undefined}} ->
+ lists:keyreplace(Mod, 1, Vsns, {Mod, OldVsn, NewVsn});
+ false ->
+ [{Mod, undefined, NewVsn} | Vsns]
+ end.
+
+
+
+%%-----------------------------------------------------------------
+%% Func: get_vsn/1
+%% Args: File = string()
+%% Purpose: Finds the version attribute of a module.
+%% Returns: Vsn
+%% Vsn = term()
+%%-----------------------------------------------------------------
+get_vsn(File) ->
+ {ok, {_Mod, Vsn}} = beam_lib:version(File),
+ case misc_supp:is_string(Vsn) of
+ true ->
+ Vsn;
+ false ->
+ %% If -vsn(Vsn) defines a term which is not a
+ %% string, the value is returned here as [Vsn].
+ case Vsn of
+ [VsnTerm] ->
+ VsnTerm;
+ _ ->
+ Vsn
+ end
+ end.