fxn
Running code when a script exits
I have a script executed with mix run that has an infinite loop that collects some data in an agent. I’d like to print that data when I interrupt the script.
Tried System.at_exit/1 without success, maybe because the only way I know to interrupt the execution is C-c C-c.
Is there a way to accomplish that?
Most Liked
josevalim
There are two questions here:
-
How to shut the system down cleanly
-
How to handle certain signals (interrupt)
To answer 1, System.stop/1 is the answer. If terminate/2 callbacks are not invoked, make sure those processes are trapping exits.
To answer 2, since Erlang/OTP 20, there is OS signal handling: http://erlang.org/doc/man/kernel_app.html#os-signal-event-handler
It is a bit of a long answer. In a nutshell, you need to implement a gen_event handler and react to the callbacks you are interested. There is a more detailed guide here: https://medium.com/@ellispritchard/graceful-shutdown-on-kubernetes-with-signals-erlang-otp-20-a22325e8ae98
josevalim
Another trick you could do is System.at_exit(fn int -> your_code(); System.stop(int) end). 
fxn
I have investigated a bit the solution with the signal handler.
Erlang executes :init.stop() on SIGTERM by default. That means that a simple kill PID shuts the thing down orderly.
However, you need to figure out the PID of the process, and open a different terminal to execute the command. Besides, custom code passed to System.at_exit/1 is not invoked by the event handler.
Ctrl-C sends SIGINT. The only shortcut that I’ve seen sends a different signal is Ctrl-\, which sends SIGQUIT. So, I am using this event handler:
defmodule QuitScript do
@behaviour :gen_event
def init(_), do: {:ok, nil}
def handle_event(:sigquit, _state), do: :remove_handler
def handle_event(signal, state), do: :erl_signal_handler.handle_event(signal, state)
def handle_call(_, state), do: {:ok, :ok, state}
def terminate(_reason, _state) do
IO.puts("My exit code")
System.stop()
end
end
:gen_event.swap_handler(:erl_signal_server, {:erl_signal_handler, []}, {QuitScript, []})
Notice that the terminate/2 callback allows you to run custom code on exit.
If you throw that to a script and execute it for example like this
mix run --no-halt foo.exs
Ctrl-C still behaves as always, and additionally Ctrl-\ shuts the thing down orderly (you only see ^\ echoed in the terminal, not too bad).
fxn
Alternatively, we could move System.stop() to the callback:
defmodule QuitScript do
@behaviour :gen_event
def init(_), do: {:ok, nil}
def handle_event(:sigquit, _state), do: System.stop()
def handle_event(signal, state), do: :erl_signal_handler.handle_event(signal, state)
def handle_call(_, state), do: {:ok, :ok, state}
def terminate(_reason, _state) do
IO.puts("My exit code")
end
end
:gen_event.swap_handler(:erl_signal_server, {:erl_signal_handler, []}, {QuitScript, []})
Looks more natural to me this way, though in the previous example the order in which the custom code runs is more obvious.
rvirding
A thing to realise here is that the handling of Ctrl-G and its commands only work when you are running a shell. Without a shell this interface is not generally not started. You can see this if you look in the elixir shell script and see that when an iex shell is not start then there is a -noshell argument to the resultant erl command. Doing elixir foo.exs does not start a shell and, hence, no handling of Ctrl-G and q.







