autodidaddict
I’ve reviewed the other topics here and done a bunch of googling and haven’t been able to find out the right way to deal with this.
I have DynamicSupervisors , about 4-5 of them. Each of those spawns GenServers as children via DynamicServer.start_child.
What I then need to be able to do is expose a function in the supervisor that will terminate a child by key, so it needs to: a) look up the child (this works fine), b) terminate the child.
The problem is I can’t figure out how to “cleanly” and “idiomatically” terminate the child process. In my terminate_child_by_key(foo) function, what’s the accepted way of shutting that pid down? Not only do I need to shut that pid down, but I need to be able to publish a message on my broker so that I can emit the “child died” event.
Since I’m going to do this over and over again, I want to do it right. I’ve tried using Process.flag(..) in the child as a way of getting advance notice that the child is going to die but that handle_info never gets called. When I use process flag in the supervisor, the supervisor never gets called during child death.
Additionally, for this scenario, I can’t use transient children (?) because they come back immediately after I kill them… the behavior I want is when I choose to kill the child, it’ll stay dead, but when it dies due to exception failure, it’ll restart.
Any advice would be greatly appreciated as I’ve been doing this all in a very ugly fashion and I don’t want to continue repeating that same ugliness all over my code base.
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 8- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
axelson
Have you tried using
DynamicSupervisor.terminate_child/2?If you’re stopping a
transientchild cleanly then it should not be restarted.ityonemo
I think Genservers already have that process flag set and are designed to trap exit messages and handle them with the terminate/2 callback.
ityonemo
also I would say the idiomatic thing to do in elixir is to spin up a Registry and send a command to the GenServer for them to just self-destruct (something like
def handle_call(:stop, _, state), do: {:stop, :normal, :ok, state}). Trawling through a supervisor’s child list seems like a very ungraceful thing to do.As an aside, note that the reason for termination matters. IIRC you should supervise
:transientbut note that the “reason” for shutdown matters;:normalor:shutdownwill not trigger restart, everything else (exceptions, kills, brutal_kills, custom reasons) will.al2o3cr
If the process is actively handling messages (versus being blocked in a
callor similar), consider adding an explicit “hey could you please shut down” message to the GenServer’s public API. Thehandle_callhead for that can return{:stop, :shutdown, :ok, state}and the GenServer will exit with reason:shutdown.Otherwise, signaling from outside is done via
Process.exit/2and functions built using it. It’s useful to understand exactly what “signaling a process to exit means”:If any Erlang process gets an exit signal with a reason of
:kill, it will exit immediately with the reason:kill.If a process that isn’t trapping exits gets an exit signal, it will exit immediately with the same reason.
If a GenServer is trapping exits, the built-in handler from
gen_serverwill invoke theterminate/2callback with the reason. (more info)(the above is summarized from The many and varied ways to kill an OTP Process | The log of Paul Wilson )
The idiomatic sequence implemented by
terminate_childinDynamicSupervisorandSupervisoris:Process.exit(pid, :shutdown):DOWNmessageProcess.exit(pid, :kill)Prefer using those functions over building custom
Process.exitsetups unless you have a real good reason.Re: reanimating processes -
restart: :transientwill restart the process if it exits with a reason other than:normal,:shutdown, or{:shutdown, term()}- for instance, if the process doesn’t respond to the:shutdownsignal and gets killed. If that’s undesirable, considerrestart: :temporaryinstead.One final note: pay close attention to the gotchas listed in the
terminatecallback’s documentation. If you need a 100% reliable “GenServer went away” hook, consider usingProcess.monitorand handling the{:DOWN, ...}message.autodidaddict
Thanks for the tip. I’ll try and combine the use of
DynamicSupervisor.terminate_childand a registry and see if that gives me the kind of cleanliness of code I’m looking for.ityonemo
Ah, just fyi you don’t have to combine them. If you use DynamicSupervisor.terminate_child, you don’t need to use Registry, and vice versa. I think there are two major differences:
In either case, be mindful of the {:normal/:shutdown}/:kill/everything-else semantics with respect to restart logic.
autodidaddict
Thanks for all the information here! I ended up deciding not to use
Process.exit. Instead I followed @ityonemo 's suggestion of sending a “soft halt” by doing aGenServer.call(pid, :halt_and_cleanup), this gives me thehandle_callfor this explicit type of termination, which in turn lets me publish the “child died” message on my broker and then safely return{:stop, :normal, :ok, state}.I also adopted the use of the Registry and I’m now storing the child pids there along with their keys.
ityonemo
Nice! I feel like this is the idiomatic Elixir solution.