erlang / rebar3

Erlang build tool that makes it easy to compile and test Erlang applications and releases.
http://www.rebar3.org
Apache License 2.0
1.7k stars 518 forks source link

How to execute a Function just before aborting rebar3 shell? #2873

Closed mrramachari closed 7 months ago

mrramachari commented 7 months ago

I have an erlang supervsior,and an application.I want a function to be executed just when I close or abort the rebar3 shell.

-module(my_web_app_sup).

-behaviour(supervisor).

-export([start_link/0]).

-export([init/1]).

-define(SERVER, ?MODULE).

start_link() ->

    supervisor:start_link({local, ?SERVER}, ?MODULE, []).

init([]) ->
    SupFlags = #{strategy => one_for_all,
                 intensity => 0,
                 period => 1},
    ChildSpecs = [],
    process_flag(trap_exit, true),
    MonitorPid = spawn(fun() -> monitor_process() end),
  % Link the monitor process to the supervisor
    link(MonitorPid),
    % system_flag(trap_exit, true),
    {ok, {SupFlags, ChildSpecs}}.

monitor_process() ->
    receive

        {'EXIT', _, _} ->
            io:format("Exit signal received. Goodbye!\n"),
            ok
    end.
ferd commented 7 months ago

That will depend on how you shut down the shell, and where the function runs.

If you use init:stop() or init:stop(0), the signal is going to propagate down from the application controller and to individual supervisors, which will send a shutdown signal to individual OTP processes. If the child traps exits, then you will be able to handle the shutdown in the terminate callback, but it will be indistinct from any other termination and invisible to a brutal shutdown of the shell (eg. with ctrl+c in the escript or whatever).

Going through all the ways a shell can be terminated of that kind is a bit more work and I'd redirect you to the erlang forums about that, because it covers a lot more ground than rebar3 itself (which is just a tiny hack over escripts to simulate a full shell but can't cover all the capabilities of it)

Another option that exists and might be more in line with what you look for is os:set_signal/2 along with registering a event handler in erl_signal_server.

mrramachari commented 7 months ago

Thank your for your reply,Yes I'm trying to execute a function just before I brutally terminate the rebar3 shell using abort option. I went through os:set_signal/2 func ,but im facing trouble trying to understand it ,perhaps if you could give me an example of how to do it ,it will be very helpful for me,thank you,below is a code snippet which I tried.

-module(my_web_app_sup).

-behaviour(supervisor).

-export([start_link/0]).

-export([init/1]).

-define(SERVER, ?MODULE).

start_link() ->

    supervisor:start_link({local, ?SERVER}, ?MODULE, []).

init([]) ->
    SupFlags = #{strategy => one_for_all,
                 intensity => 0,
                 period => 1},
    ChildSpecs = [],
process_flag(trap_exit, true),
RestartStrategy = {one_for_one, 8, 10},
  MonitorPid = spawn(fun() -> handle() end),
        link(MonitorPid),
     os:set_signal('sigtstp', 'handle'), 
    {ok, {SupFlags, ChildSpecs}}.

handle() ->
    receive

        'sigtstp' ->
            io:format("Exit signal received. Goodbye!\n")
             end.
ferd commented 7 months ago

so you're calling os:set_signal/2 which makes the signal go to the erl_signal_server process. That process is a gen_event worker, which means you would have to register a callback module for it.

This requires defining two callbacks at least: init and handle_event, and then registering it. There's a more complete tutorial in the official docs if you want, and an older one in LYSE.

mrramachari commented 7 months ago

Thank you for your reply once again, actually my main goal is to execute a cleanup type function which I can use to truncate a table of a database or something like that,I want that to happen when the shell gets closed.I tried doing the gen_event module but still I'm facing difficulties to start it from the supervisor, also if i start it manually its not capturing the exit signals when i abort.

This is my supervisor

-module(my_web_app_sup).

-behaviour(supervisor).

-export([start_link/0]).

-export([init/1]).

-define(SERVER, ?MODULE).

start_link() ->

    supervisor:start_link({local, ?SERVER}, ?MODULE, []).

init([]) ->
     TerminalLogger = {terminal_logger, {terminal_logger, start_link, []}, permanent, 2000, worker, [terminal_logger]}, % Add terminal logger
      % gen_event:start({local, error_man}),
      % gen_event:add_handler(error_man, terminal_logger, []),
   Childern = [TerminalLogger],
RestartStrategy = {one_for_one, 8, 10},
    {ok, {RestartStrategy, Childern}}.

handle() ->
    receive        
        'sigtstp' ->
            io:format("Exit signal received. Goodbye!\n")
             end.

this is my terminal_logger which is a gen_event module

-module(terminal_logger).
-moduledoc false.
-behaviour(gen_event).
-export([start/0, init/1,
         handle_event/2, handle_call/2, handle_info/2,
         terminate/2, code_change/3]).

-record(state,{}).

start() ->
    %% add signal handler
    case whereis(erl_signal_server) of
        %% in case of minimal mode
        undefined -> ok;
        _ ->
            gen_event:add_handler(erl_signal_server, terminal_logger, [])
    end.

init(_Args) ->
    {ok, #state{}}.

handle_event(sigusr1, S) ->
    erlang:halt("Received SIGUSR1"),
    {ok, S};
handle_event(sigquit, S) ->
    erlang:halt(),
    {ok, S};
handle_event(sigterm, S) ->
    error_logger:info_msg("SIGTERM received - shutting down~n"),
    ok = init:stop(),
    {ok, S};
handle_event(_SignalMsg, S) ->
            io:format("~n handle_event(_SignalMsg, S) ~n"),

    {ok, S}.

handle_info(_Info, S) ->
        io:format("~n handle_info(_Info, S) ->~n"),
    {ok, S}.

handle_call(_Request, S) ->
            io:format("~n handle_call(_Request, S)  ~n"),

    {ok, ok, S}.

code_change(_OldVsn, S, _Extra) ->
    {ok, S}.

terminate(_Args, _S) ->
        io:format("~nterminated~n"),

    ok.

This is my terminal output

rebar3 shell
===> Verifying dependencies...
===> Analyzing applications...
src/amf_sup.erl:49:1: Warning: function terminate/2 is unused

Erlang/OTP 26 [erts-14.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
1> terminal_logger:start().
ok
2> 
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
       (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
a
ferd commented 7 months ago

Your supervisor is going for {terminal_logger, start_link, []} but the function exported is terminal_logger:start/0. You also do not need to supervise the callback module because it should run within erl_signal_server.

At this point though I'm thinking this is more general Erlang help you require and this is off-topic for the rebar3 build tool. I would encourage you to move the discussion somewhere like erlangforums.com.

mrramachari commented 7 months ago

Thank you.