fix(emqx_machine): ensure digraph is deleted after use
also add tests
This commit is contained in:
parent
75f9741d75
commit
5063d3a2b3
|
@ -27,6 +27,10 @@
|
||||||
|
|
||||||
-export([sorted_reboot_apps/0]).
|
-export([sorted_reboot_apps/0]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-export([sorted_reboot_apps/1]).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
%% @doc EMQ X boot entrypoint.
|
%% @doc EMQ X boot entrypoint.
|
||||||
|
@ -150,6 +154,7 @@ app_deps(App) ->
|
||||||
|
|
||||||
sorted_reboot_apps(Apps) ->
|
sorted_reboot_apps(Apps) ->
|
||||||
G = digraph:new(),
|
G = digraph:new(),
|
||||||
|
try
|
||||||
lists:foreach(fun({App, Deps}) -> add_app(G, App, Deps) end, Apps),
|
lists:foreach(fun({App, Deps}) -> add_app(G, App, Deps) end, Apps),
|
||||||
case digraph_utils:topsort(G) of
|
case digraph_utils:topsort(G) of
|
||||||
Sorted when is_list(Sorted) ->
|
Sorted when is_list(Sorted) ->
|
||||||
|
@ -157,6 +162,9 @@ sorted_reboot_apps(Apps) ->
|
||||||
false ->
|
false ->
|
||||||
Loops = find_loops(G),
|
Loops = find_loops(G),
|
||||||
error({circular_application_dependency, Loops})
|
error({circular_application_dependency, Loops})
|
||||||
|
end
|
||||||
|
after
|
||||||
|
digraph:delete(G)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
add_app(G, App, undefined) ->
|
add_app(G, App, undefined) ->
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_machine_tests).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
sorted_reboot_apps_test_() ->
|
||||||
|
Apps1 = [{1, [2, 3, 4]},
|
||||||
|
{2, [3, 4]}
|
||||||
|
],
|
||||||
|
Apps2 = [{1, [2, 3, 4]},
|
||||||
|
{2, [3, 4]},
|
||||||
|
{5, [4, 3, 2, 1, 1]}
|
||||||
|
],
|
||||||
|
[fun() -> check_order(Apps1) end,
|
||||||
|
fun() -> check_order(Apps2) end
|
||||||
|
].
|
||||||
|
|
||||||
|
sorted_reboot_apps_cycle_test() ->
|
||||||
|
Apps = [{1,[2]},{2, [1,3]}],
|
||||||
|
?assertError({circular_application_dependency, [[1, 2, 1], [2, 1, 2]]},
|
||||||
|
check_order(Apps)).
|
||||||
|
|
||||||
|
|
||||||
|
check_order(Apps) ->
|
||||||
|
AllApps = lists:usort(lists:append([[A | Deps] || {A, Deps} <- Apps])),
|
||||||
|
Sorted = emqx_machine:sorted_reboot_apps(Apps),
|
||||||
|
case length(AllApps) =:= length(Sorted) of
|
||||||
|
true -> ok;
|
||||||
|
false -> error({AllApps, Sorted})
|
||||||
|
end,
|
||||||
|
{_, SortedWithIndex} =
|
||||||
|
lists:foldr(fun(A, {I, Acc}) -> {I + 1, [{A, I} | Acc]} end, {1, []}, Sorted),
|
||||||
|
do_check_order(Apps, SortedWithIndex).
|
||||||
|
|
||||||
|
do_check_order([], _) -> ok;
|
||||||
|
do_check_order([{A, Deps} | Rest], Sorted) ->
|
||||||
|
case lists:filter(fun(Dep) -> is_sorted_before(Dep, A, Sorted) end, Deps) of
|
||||||
|
[] -> do_check_order(Rest, Sorted);
|
||||||
|
Bad -> throw({A, Bad})
|
||||||
|
end.
|
||||||
|
|
||||||
|
is_sorted_before(A, B, Sorted) ->
|
||||||
|
{A, IndexA} = lists:keyfind(A, 1, Sorted),
|
||||||
|
{B, IndexB} = lists:keyfind(B, 1, Sorted),
|
||||||
|
IndexA < IndexB.
|
Loading…
Reference in New Issue