smpallen99

smpallen99

Beginner Hint: Using GenServers

Some advice for Elixir programmers.

I was reviewing someone’s Elixir Code yesterday and found a deadlock condition bug in a GenServer implementation. That’s not a big deal, since I’ve created one or two in the past (while not paying attention). However, I was surprised how long it took me to explain the issue to someone that has been coding in Elixir for a number of years now. I had go back to first principles and explain how servers work in Elixir.

So, here’s my Tip:

Learn how to write your own servers with both synchronous (GenServer.call/2) and asynchronous (GenServer.cast/2) APIs before using GenServers. GenServers provide a convenient API with lots of functionality behind the scene. However, to avoid potential concurrency issues, you should have an understanding of how they work internally. It will help you reason with the message serialization and know when to use GenServer.call/2 vs GenServer.cast/2. Especially, when not to use GenServer.call/2.

If you are still reading this, and have not figured out how deadlock may happen, then I’ll elaborate. Elixir and Erlang message passing is asynchronous, Full stop. When you send a message, the sender does not wait for the destination process to receive and/or process the message. To do something, the caller sends an async message to another process and includes their own pid in the payload of the message and then calls receive, blocking on a response from the target process.

If you send a sync message using this model (same as a GenServer.call/2) to yourself (send and receive processes are the same pid), then you will deadlock.

This has some implications on how to design and use API functions and helpers in your GenServers. Here are my rules:

  • Never call your public APIs (in the same module) from any call back or helper code
  • Only ever use GenServer.call/2 (to same process) in your public APIs.
  • If you want to share code between an API and a helper, factor out the common code into a separate function and call it from each.
  • Think hard every time you do a sync call from one GenServer to another First, It could lead to a deadlock, if it results into a sync call back into the original caller. But equally important you may be creating a sterilizing bottleneck that may have negative throughout implications.

Most Liked

rvirding

rvirding

Creator of Erlang

I think one reason for this problem is where you come from when you get to a GenServer. What do I mean?

If you are coming bottom-up so to speak from the basic concurrency with processes, messages, receive etc, then you know that a GenServer is just a process and the call and cast are just sending messages. Then know that doing a call just sends a message to yourself and so waiting for a reply is just ridiculous.

If however you come top-down then you see the GenServer through its function interface and the concurrent nature is hidden. I am just calling the GenServer should why shouldn’t I be able to call myself. Coming from an OO background and then “seeing” a GenServer as an object strengthens this view.

That the GenServer, and other behaviours, put effort into hiding the concurrency does not help in this respect. Unfortunately you can not avoid the concurrent nature of the erlang/elixir systems.

Though I must add I have seen people who do realise GenServers are concurrent processes still get into a lot of problems when they try to do calls between servers and find that they block. This I think is more based on not being used to thinking concurrently.

10
Post #7
peerreynders

peerreynders

i.e. when talking to yourself use GenServer.cast/2.

smpallen99

smpallen99

I’m not sure that I’ve found a good reference for this, but its been 4 years since I went through that learning process. There are two basic approaches for starting GenServers. First, you can start them from a supervisor that runs at startup. In this care they will be named (which is not your question).

The second approach is to start them from another stateful process (Another GenServer, GenFSM, or GenStatem. In this case you will need to store the pid as part of that server’s state. When I need to have multiple processes access a common GenServer, I will create a “Manager” that uses a Map to map an id to the PID.

The other I have done is to propagate the pid to all processes that need the pid. But then you need to handle updating that pid if the server restarts.

This isn’t too difficult, you just need to trap exits on the process so you will receive a :DOWN message.

Also, when starting dynamic servers, I don’t usually run start_link directly, but create an API on a supervisor to start the process.

One thing that you have to watch out for is providing a pid in the start up arguments of a supervised GenServer. When a supervised process restarts, it receives the original arguments provide on its initial startup. So, if you provide the pid of some process and that process has restarted, then the pid will be stale. I remember struggling with this for a while when I was starting out with elixir.

bill.c

bill.c

Very helpful information. I am still challenged with implementing GenServers with what I perceive as beginner level use cases. For example, I want to use a non-named GenServer (the traditional start and get a PID reference) but do not fully understand the model for storing and referencing this PID in a working application.

Is there a reference example you recommend for a model GenServer implementation? I have read the Elixir-lang doc example, but this describes building a GenServer and not necessarily using a GenServer in depth.

Or, perhaps, there is a book I have not yet purchased or one I need to re-examine?

If nothing else, I think your forum topic is a great start for experts to collect a FAQ or an Awesome GenServer list.

peerreynders

peerreynders

Essentially “strangers” will need a name/registry to find you. A process tends to want to “remember” the PIDs of the processes it creates and any process expecting a reply will have to supply it’s PID (something GenServer.call will do automatically for you).

Is there a reference example you recommend for a model GenServer implementation?

Have you gone through Introduction to Mix?
Supervisor and Applications discusses named processes.

Where Next?

Popular in Guides/Tuts Top

Logan
Here is an example of a Mix application that utilizes Cowboy to handle websocket connections. If anyone has an idea about making this wor...
New
njwest
Greetings: I just wrote a step-by-step guide on building a Phoenix 1.3 JWT Auth API with Guardian JWTs and Comeonin password hashing. I ...
New
ob1
Recently we partitioned a table with 28 million rows by its ULID to improve query speeds and reduce query timeouts. To do this I ended up...
New
jshprentz
Geoffrey Lessel’s 2019 book, Phoenix in Action, was written for Phoenix 1.4. I found that the book’s code examples did not match the cur...
New
zenw0lf
Hello all! For those wanting to try your hands at Elixir / Phoenix, I wrote a comprehensive tutorial on doing a simple JSON API with sai...
New
ben-pr-p
Hey all! I put together a starter-pack / instructions to set up Phoenix with the new parcel-bundler instead of brunch (or webpack). It’s...
New
alejandroErik
POST IN CONSTRUCTION Process for compile erlang otp 20 with odbc-unix for Oracle connections on Solaris 11.3 for 64 bits: Introductio...
New
anuragg
We just published a guide to automatic clustering in Elixir 1.9, with Mix releases and libcluster. The cluster automatically discovers n...
New
slouchpie
Warmest greetings, comrades. I recently started using :dns_cluster (GitHub - phoenixframework/dns_cluster: Simple DNS clustering for dis...
New
anuragg
We finally have a Mix clustering guide to go with Phoenix deployment with Mix Releases. Deploy an Elixir Cluster with Mix Releases and l...
New

Other popular topics Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

We're in Beta

About us Mission Statement