فهرست منبع

fix(emqx_machine): ensure digraph is deleted after use

also add tests
Zaiming Shi 4 سال پیش
والد
کامیت
5063d3a2b3
2فایلهای تغییر یافته به همراه75 افزوده شده و 7 حذف شده
  1. 15 7
      apps/emqx_machine/src/emqx_machine.erl
  2. 60 0
      apps/emqx_machine/test/emqx_machine_tests.erl

+ 15 - 7
apps/emqx_machine/src/emqx_machine.erl

@@ -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,13 +154,17 @@ app_deps(App) ->
 
 
 sorted_reboot_apps(Apps) ->
 sorted_reboot_apps(Apps) ->
     G = digraph:new(),
     G = digraph:new(),
-    lists:foreach(fun({App, Deps}) -> add_app(G, App, Deps) end, Apps),
-    case digraph_utils:topsort(G) of
-        Sorted when is_list(Sorted) ->
-            Sorted;
-        false ->
-            Loops = find_loops(G),
-            error({circular_application_dependency, Loops})
+    try
+        lists:foreach(fun({App, Deps}) -> add_app(G, App, Deps) end, Apps),
+        case digraph_utils:topsort(G) of
+            Sorted when is_list(Sorted) ->
+                Sorted;
+            false ->
+                Loops = find_loops(G),
+                error({circular_application_dependency, Loops})
+        end
+    after
+        digraph:delete(G)
     end.
     end.
 
 
 add_app(G, App, undefined) ->
 add_app(G, App, undefined) ->

+ 60 - 0
apps/emqx_machine/test/emqx_machine_tests.erl

@@ -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.