emqx_machine_terminator.erl 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2021-2024 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_terminator).
  17. -behaviour(gen_server).
  18. -export([
  19. start_link/0,
  20. graceful/0,
  21. graceful_wait/0,
  22. is_running/0
  23. ]).
  24. -export([
  25. init/1,
  26. format_status/2,
  27. handle_cast/2,
  28. handle_call/3,
  29. handle_info/2,
  30. terminate/2,
  31. code_change/3
  32. ]).
  33. -include_lib("emqx/include/logger.hrl").
  34. -define(TERMINATOR, ?MODULE).
  35. -define(DO_IT, graceful_shutdown).
  36. %% @doc This API is called to shutdown the Erlang VM by RPC call from remote shell node.
  37. %% The shutdown of apps is delegated to a to a process instead of doing it in the RPC spawned
  38. %% process which has a remote group leader.
  39. start_link() ->
  40. {ok, _} = gen_server:start_link({local, ?TERMINATOR}, ?MODULE, [], []).
  41. is_running() -> is_pid(whereis(?TERMINATOR)).
  42. %% @doc Call `emqx_machine_terminator' to stop applications
  43. %% then call init:stop() stop beam.
  44. graceful() ->
  45. try
  46. _ = gen_server:call(?TERMINATOR, ?DO_IT, infinity)
  47. catch
  48. _:_ ->
  49. %% failed to notify terminator, probably due to not started yet
  50. %% or node is going down, either case, the caller
  51. %% should issue a shutdown to be sure
  52. %% NOTE: not exit_loop here because we do not want to
  53. %% block erl_signal_server
  54. ?ELOG("Shutdown before node is ready?~n", []),
  55. init:stop()
  56. end,
  57. ok.
  58. %% @doc Shutdown the Erlang VM and wait indefinitely.
  59. graceful_wait() ->
  60. ?AUDIT(alert, #{
  61. cmd => emqx,
  62. args => [<<"stop">>],
  63. version => emqx_release:version(),
  64. from => cli,
  65. duration_ms => element(1, erlang:statistics(wall_clock))
  66. }),
  67. ok = graceful(),
  68. exit_loop().
  69. exit_loop() ->
  70. timer:sleep(100),
  71. init:stop(),
  72. exit_loop().
  73. init(_) ->
  74. ok = emqx_machine_signal_handler:start(),
  75. {ok, #{}}.
  76. handle_info(_, State) ->
  77. {noreply, State}.
  78. handle_cast(_Cast, State) ->
  79. {noreply, State}.
  80. handle_call(?DO_IT, _From, State) ->
  81. try
  82. %% stop port apps before stopping other apps.
  83. emqx_machine_boot:stop_port_apps(),
  84. emqx_machine_boot:stop_apps()
  85. catch
  86. C:E:St ->
  87. Apps = [element(1, A) || A <- application:which_applications()],
  88. ?SLOG(error, #{
  89. msg => "failed_to_stop_apps",
  90. exception => C,
  91. reason => E,
  92. stacktrace => St,
  93. remaining_apps => Apps
  94. })
  95. after
  96. init:stop()
  97. end,
  98. {reply, ok, State};
  99. handle_call(_Call, _From, State) ->
  100. {noreply, State}.
  101. format_status(_Opt, [_Pdict, _S]) ->
  102. ok.
  103. code_change(_OldVsn, State, _Extra) ->
  104. {ok, State}.
  105. terminate(_Args, _State) ->
  106. ok.