sheharyarn
Graceful Shutdown of GenServers
I writing an Elixir app with GenServer that starts an external application on boot and shuts it down and does other clean-up on exit. I’ve added bootup functionality in the init/1 callback and cleanup code in the terminate/2 callback.
The init code works fine when the GenServer is started, and the terminate method is also called when the :stop signal is manually sent, but in the cases of unexpected shutdowns and interrupts (as in the case of hitting Ctrl+C) in IEx, the terminate code is not called.
What’s the proper way of doing cleanup when my Elixir app crashes or is unexpectedly shutdown?
Here’s the parallel StackOverflow Question and my code:
defmodule MyAwesomeApp do
use GenServer
def start do
GenServer.start_link(__MODULE__, nil)
end
def init(state) do
# Do Bootup stuff
IO.puts "Starting: #{inspect(state)}"
{:ok, state}
end
def terminate(reason, state) do
# Do Shutdown Stuff
IO.puts "Going Down: #{inspect(state)}"
:normal
end
end
MyAwesomeApp.start
Marked As Solved
sasajuric
To increase chances of the terminate callback being invoked, the server process should trap exits. However, even with that, the callback might not be invoked in some situations (e.g. when the process is brutally killed, or when it crashes itself). For more details see here.
As mentioned, if you want to politely shutdown your system, you should invoke :init.stop, which will recursively shutdown the supervision tree causing terminate callbacks to be invoked.
As you noticed, there is no way of catching abrupt BEAM OS process exits from within. It’s a self-defining property: the BEAM process terminates suddenly, so it can’t run any code (since it terminated)
Hence, if BEAM is brutally terminated, the callback will not be invoked.
If you unconditionally want to do something when BEAM dies, you need to detect this from another OS process. I’m not sure what’s your exact use case, but assuming you have some strong needs for this, then running another BEAM node, on the same (or another) machine, could work here. Then you could have one process on one node monitoring another process on another node, so you can react even if BEAM is brutally killed.
However, your life will be simpler if you don’t need to unconditionally run some cleanup logic, so consider whether the code in terminate is a must, or rather a nice-to-have?
Also Liked
OvermindDL1
The ‘proper’ way to shut down a node from the console (or in code) would be :init.stop(), which can take an optional integer that is the return code of the program. Ctrl+c is a harsh stop (I wish iex would bind it to :init.stop() with, say, a 30 second timeout that counts down on the screen, maybe displaying what it is waiting on to top too).
pma
If the Erlang VM stops, the external port process will receive a signal indicating that the stdin closed.
If ngrok doesn’t gracefully stop on CTRL+D, you can use a small bash wrapper to handle the stdin closing and then killing ngrok with the appropriate signal.
sasajuric
If the Erlang VM stops, the external port process will receive a signal indicating that the stdin closed.
This is the correct hint. The external process will get EOF on its stdin. However, if the external program is busy doing something else, it could linger for much longer before it detects that. I’ve written a bit about this here (see “Program Termination” section).
IMO, the cleanest solution, if you own the code of the external program, is to adapt it to run processing in a separate thread, while the main thread just does I/O. That way, the external program can immediately detect the termination of the other side, and terminate itself immediately.
If that’s not an option, I think (but not sure) that Porcelain by @alco might offer some automagical help.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









