emqx_machine_boot.erl 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2021-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_machine_boot).
  17. -include_lib("emqx/include/logger.hrl").
  18. -export([post_boot/0]).
  19. -export([stop_apps/0, ensure_apps_started/0]).
  20. -export([sorted_reboot_apps/0]).
  21. -export([start_autocluster/0]).
  22. -ifdef(TEST).
  23. -export([sorted_reboot_apps/1]).
  24. -endif.
  25. post_boot() ->
  26. ok = ensure_apps_started(),
  27. ok = print_vsn(),
  28. ok = start_autocluster(),
  29. ignore.
  30. -ifdef(TEST).
  31. print_vsn() -> ok.
  32. -else. % TEST
  33. print_vsn() ->
  34. ?ULOG("~ts ~ts is running now!~n", [emqx_app:get_description(), emqx_app:get_release()]).
  35. -endif. % TEST
  36. start_autocluster() ->
  37. ekka:callback(stop, fun emqx_machine_boot:stop_apps/0),
  38. ekka:callback(start, fun emqx_machine_boot:ensure_apps_started/0),
  39. _ = ekka:autocluster(emqx), %% returns 'ok' or a pid or 'any()' as in spec
  40. ok.
  41. stop_apps() ->
  42. ?SLOG(notice, #{msg => "stopping_emqx_apps"}),
  43. _ = emqx_alarm_handler:unload(),
  44. lists:foreach(fun stop_one_app/1, lists:reverse(sorted_reboot_apps())).
  45. stop_one_app(App) ->
  46. ?SLOG(debug, #{msg => "stopping_app", app => App}),
  47. try
  48. _ = application:stop(App)
  49. catch
  50. C : E ->
  51. ?SLOG(error, #{msg => "failed_to_stop_app",
  52. app => App,
  53. exception => C,
  54. reason => E})
  55. end.
  56. ensure_apps_started() ->
  57. ?SLOG(notice, #{msg => "(re)starting_emqx_apps"}),
  58. lists:foreach(fun start_one_app/1, sorted_reboot_apps()).
  59. start_one_app(App) ->
  60. ?SLOG(debug, #{msg => "starting_app", app => App}),
  61. case application:ensure_all_started(App) of
  62. {ok, Apps} ->
  63. ?SLOG(debug, #{msg => "started_apps", apps => Apps});
  64. {error, Reason} ->
  65. ?SLOG(critical, #{msg => "failed_to_start_app", app => App, reason => Reason}),
  66. error({failed_to_start_app, App, Reason})
  67. end.
  68. %% list of app names which should be rebooted when:
  69. %% 1. due to static config change
  70. %% 2. after join a cluster
  71. reboot_apps() ->
  72. [ gproc
  73. , esockd
  74. , ranch
  75. , cowboy
  76. , emqx
  77. , emqx_prometheus
  78. , emqx_modules
  79. , emqx_dashboard
  80. , emqx_connector
  81. , emqx_gateway
  82. , emqx_statsd
  83. , emqx_resource
  84. , emqx_rule_engine
  85. , emqx_bridge
  86. , emqx_plugin_libs
  87. , emqx_management
  88. , emqx_retainer
  89. , emqx_exhook
  90. , emqx_authn
  91. , emqx_authz
  92. , emqx_plugins
  93. ].
  94. sorted_reboot_apps() ->
  95. Apps = [{App, app_deps(App)} || App <- reboot_apps()],
  96. sorted_reboot_apps(Apps).
  97. app_deps(App) ->
  98. case application:get_key(App, applications) of
  99. undefined -> undefined;
  100. {ok, List} -> lists:filter(fun(A) -> lists:member(A, reboot_apps()) end, List)
  101. end.
  102. sorted_reboot_apps(Apps) ->
  103. G = digraph:new(),
  104. try
  105. NoDepApps = add_apps_to_digraph(G, Apps),
  106. case digraph_utils:topsort(G) of
  107. Sorted when is_list(Sorted) ->
  108. %% ensure emqx_conf boot up first
  109. [emqx_conf | Sorted ++ (NoDepApps -- Sorted)];
  110. false ->
  111. Loops = find_loops(G),
  112. error({circular_application_dependency, Loops})
  113. end
  114. after
  115. digraph:delete(G)
  116. end.
  117. %% Build a dependency graph from the provided application list.
  118. %% Return top-sort result of the apps.
  119. %% Isolated apps without which are not dependency of any other apps are
  120. %% put to the end of the list in the original order.
  121. add_apps_to_digraph(G, Apps) ->
  122. lists:foldl(fun
  123. ({App, undefined}, Acc) ->
  124. ?SLOG(debug, #{msg => "app_is_not_loaded", app => App}),
  125. Acc;
  126. ({App, []}, Acc) ->
  127. Acc ++ [App]; %% use '++' to keep the original order
  128. ({App, Deps}, Acc) ->
  129. add_app_deps_to_digraph(G, App, Deps),
  130. Acc
  131. end, [], Apps).
  132. add_app_deps_to_digraph(G, App, undefined) ->
  133. ?SLOG(debug, #{msg => "app_is_not_loaded", app => App}),
  134. %% not loaded
  135. add_app_deps_to_digraph(G, App, []);
  136. add_app_deps_to_digraph(_G, _App, []) ->
  137. ok;
  138. add_app_deps_to_digraph(G, App, [Dep | Deps]) ->
  139. digraph:add_vertex(G, App),
  140. digraph:add_vertex(G, Dep),
  141. digraph:add_edge(G, Dep, App), %% dep -> app as dependency
  142. add_app_deps_to_digraph(G, App, Deps).
  143. find_loops(G) ->
  144. lists:filtermap(
  145. fun (App) ->
  146. case digraph:get_short_cycle(G, App) of
  147. false -> false;
  148. Apps -> {true, Apps}
  149. end
  150. end, digraph:vertices(G)).