mickel8
Membrane Core Team
How to correctly handle GenServer.call exits because of non-existing process
GenServer.call might fail when a process we try to execute a call on no longer exists. Are there any guidelines on how to handle such situation? Let’s assume the following scenario:
defmodule RoomService do
def list_rooms() do
RoomRegistry
|> Registry.select([{{:"$1", :_, :_}, [], [:"$1"]}])
|> Enum.map(&Room.get_state(&1))
|> Enum.reject(&(&1 == nil))
end
end
defmodule Room do
def get_state(room_id) do
registry_room_id = registry_id(room_id)
GenServer.call(registry_room_id, :get_state)
end
end
WIth the above API, RoomService will crash when any of the calls to the Room.get_state crashes which is unwanted behavior.
RoomService cannot wrap Room.get_state into try catch as it is not aware of get_state internal implementation.
We could use try catch inside get_state like:
def get_state(room_id) do
registry_room_id = registry_id(room_id)
try do
GenServer.call(registry_room_id, :get_state)
catch
:exit, {:noproc, {GenServer, :call, [^registry_room_id, :get_state, _timeout]}} ->
Logger.warning(
"Cannot get state of #{inspect(room_id)}, the room's process doesn't exist anymore"
)
nil
end
end
but this effectively means that every single GenServer.call should be wrapped into try catch.
I feel like I am missing something ![]()
Trending in Questions
Hello!
Suppose you are building workflow (order / task / payment) processing system with the following requirements:
Each workflow con...
New
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
Using Phoenix.LiveView.TagEngine as an EEx.Engine is deprecated!
To compile HEEx, use Phoenix.LiveView.TagEngine.compile/2 instead.
Sta...
New
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app?
Looking for hints regarding:
Addi...
New
Hi all, I wanted to ask how the community is dealing with post-release steps.
Today we have Ecto migrations, which make sure that the db...
New
I am using Oban and occasionally, shortly after a deployment, a handful of jobs can fail because of dependency on other parts of the syst...
New
Other Trending Topics
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
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
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 10 of 19 Posts
LostKobrakai
That’s the idea. With a
GenServer.callthe caller is expected to need the response to the call. If a response is impossible to get it’s expected that the caller cannot/should not continue.mickel8
How can the
Roommodule know the way it is going to be used by other modules?Roommodule can’t know whether its caller should or shouldn’t continue when a call to the room crashes. This is caller specific behavior, especially when you are writing a library which exposes aGenServer. You can’t know how users of this library are going to use your API. Some of them might want to crash withGenServer.callcrash, some other might not.LostKobrakai
Catch the exit of
GenServer.calland give the caller of the function enough context to make their own decisions.Roomis still the module doing theGenServer.call, so it can decide how it deals with the fact that this might cause an exit.mickel8
So we end up with the solution where we wrap every single
GenServer.callwith atry catch, doesn’t we? Sounds like something we should take care of in the standard library? Something likeGenServer.callandGenServer.call!LostKobrakai
Can’t do that without breaking BC and the fact that the elixir API mirrors what
:gen_server.calldoes.lud
You can search github for
safe_calland you will find a buch of results. There may be lots of other names for that, for instance in thebrodlibrary it’s calledsafe_gen_call.As a general rule of thumb, in a library that deals with processes, you may want to do that try/catch directly in library code if the process not being alive is frequent and desirable. And in all other cases do not wrap the call because when you are calling a process in general it should be there.
A common use case that comes to mind is dealing with transient processes, or timeout-then-stop, because calling
Process.alive?before doing a call has an inherent race condition. So you just do the call, and if it exits with:noprocyou start the process and try again.So no you should not default to wrap everything, if the use case arises it’s generally pretty obvious.
mickel8
The example with
brodlibrary is great, thanks!I can imagine that you have some kind of a client, which is a process and might die when it disconnects from a remote server. In such a case, wrapping
calls to the client intotry/catchsounds good.But shouldn’t we be prepared for every process to crash at any time, e.g. because there is a bug in a code or something unexpected happened? Aren’t they reasons why we always try to spawn a process under supervisor?
LostKobrakai
Yes we should expect processes to crash. How we react on a process not being unavailable is the point of discussion though. The option choosen for
GenServer.calland (probably way) earlier for:gen_server.callis to exit if the process is not available. I’d imagine the reasoning being that the calling process requires a response to the call and if that cannot be retrieved all code depending on the response likely shouldn’t run as well. Choosing how to handle (such) errors is a tradeoff for sure, but I don’t think this one is entirely unreasonable.The idea of supervisors and OTPs fault tolerance is not that things always have fallbacks (I’d even say to the contrary), but rather that failures only affect the users, which run into a fault, but not others. For this case this means the unlucky user trying to call a crashed process will crash as well, but any user not needing to interact with that missing process is fine. Once the process is back up the system is healed and everything works again.
Another thing you can look into is the async alternative to doing a call (
send_request,wait_response, …) introduced in OTP 23, which doesn’t fail if a process is not alive, but returns an error tuple on trying to receive the response. That one is not yet in mirrored in elixir though, but the erlang api should work just fine.mat-hek
It’s a bit like saying that if a process opens a file and the file doesn’t exist, all the code dependent on the file shouldn’t run. But it doesn’t mean that the process has to crash, thus we have
File.openandFile.open!LostKobrakai
That might be a very reasonable thing to do.
I’m not argueing that having that option wouldn’t be useful - it very much would be. I don’t see a way to get there now though. The decision for
:gen_server.callhad been made ages ago andGenServer.callmirrored it. I don’t thinkGenServer.call_but_not_exitis a great option as well. Unless this discussion is aimed at elixir 2.0 I’m not sure what this topic is meant to get to.