Is GenServer used in real life applications? If so, how?

Hi! :wave:
I am quite fresh to Elixir. Coming from Python, I am going through Elixir School lessons and am trying to connect the dots in my head.

I stumbled upon a GenServer/concurrency lesson and there is an example of a queue implementation with GenServer. It seems cool, especially with the async calls built in.

What do you use it for in real life though? Could it be a web server/a message queue? Why is it important for me to know?

1 Like

GenServers aren’t just for queues, but any stateful thing you want.

Starting with the queue, why make it a separate process? If it’s only going to be used by one process, you can simply keep the queue as part of a singular process’s state, something like:

def init() do
  loop(%{queue => Queue.new(), other_state => ???})
end

def loop(state) do
  ...
end

But if you want to share that queue with other processes, you will want it to be its own process. This way you can initialize the above with something like:

def init(common_queue) do
  loop(%{queue => common_queue, other_state => +++})
end

def loop(state) do
  ...
end

Then each process can read or write to it in a controlled fashion. Think about other things you might want to control or share in a similar fashion. A use might be controlling access to a limited system resource (database connection pooling, GPU if you have some C/C++/Rust/whatever code you want to coordinate via Elixir, etc.).

Or within a system you can use GenServer to set up entities that communicate/coordinate with each other. In an HTTP context, maybe you spawn a group of processes with each connection. One dealing with the database, another with the filesystem, another with the actual HTTP connection. They’re separate because they can each fail differently and you may want to restart them independently. If the HTTP connection fails, you want everything to restart. But if the database connection fails you want to only restart that part.

The reason you should know GenServer is that it’s a commonly used element that frees you from having to consider other things as well. It’s like implementing a class in Java that implements a particular interface (or perhaps more like making a concrete implementation of an abstract class). There are parts of the behavior that you have to implement (the handle_* parts and such), but you don’t have to deal with the actual loop, receive code, dispatching messages to each type of handler, etc.

5 Likes

Honestly I would say in 80% of cases you should NOT be writing GenServers. But, a lot of things you might USE are written on top of genservers. For example, supervisors, liveview, and genstage, so it’s good to understand how genservers work, and therefore playing around with them is a great idea.

The biggest class of things that should be stateful are connections. If you are writing a low level connection, I recommend the Connection library, which is like a genserver, but not quite. The second biggest class of things are state machines modeling things IRL (as in, it needs to asynchronously check in on it’s counterpart in the real world). For this, there’s gen_statem, which is also “like a genserver, but not quite”, but less so than Connection. There are also state machines that are abstract (don’t need to check in on anything out of band), for that one should use a data structure and not a process.

For almost everything else async in elixir, use Task.

4 Likes

TBH, I think I have never used Task in production. I think that whenever I think I could use Task, I find it soon enough that raw GenServer is much better approach.

4 Likes

we’re getting a bit off-topic, but Task supports “$caller” infrastructure. This means if you want to run concurrent integration tests, a Task inherits the allowances (Mox allowances, Ecto sandboxes, for example). When testing, It’s extremely convenient to use “$caller” infrastructure to partition various bits of states and deliver to the requester only knowledge of its own “localized shard” of global state. I cheekily call this the Multiverse pattern. Is my system in prod, I have several gen_statem avatars of IRL devices in a registry, so in tests I keep the same registry, but partition the Registry ETS queries by appending $caller metadata, so each test can only see the device “fixtures” that have been created in its own test, no matter how many other tests are running concurrently. Extending it to GenServer is ugly, but possible: you can see how to I do it here (https://github.com/ityonemo/multiverses/blob/master/lib/multiverses/gen_server.ex#L1, it involves hijacking the :gen call… Not generally recommended.) Anyways, I have adapters for Registry, DynamicSupervisor, GenServer, :gen_statem, Phoenix.PubSub, Phoenix.Presence, and Finch. I’ll probably get around to Hound and Wallaby by the end of summer.

As for what I do with Tasks. In my example I talk about GenStatem making async query/command to to an item IRL, to self-update its state. This i use a task for (it’s a long running SSH remote call over the LAN, and on the other side some queries take upwards of a second). GenStatem launches a task, and at the end of the task is a call back to the statem to signal update (I probably could have use Task.Sup.async but I don’t like the “call back to genserver” semantic for Task.*.async). The boilerplate code to spawn a whole genserver module for each one of these calls (there’s about six different unique commands) seems like too, much, when “do one thing and come back to me” fits very nicely in a fun.

5 Likes

In tests, yes I agree, that Task is useful. That is why I said “in production”, I do not see tests as “production code”, because these are, well, tests.

If it’s not a Task in your main (prod-facing) code, then your allowances will get detached in your tests too. I almost never use Task in my tests. If anything I use naked spawns to explicitly detach something from my callers chain to verify orthogonality.

I have developed many Elixir and Phoenix based applications yet I still do not understand what a GenServer is. Maybe the strange name holds me back :slightly_smiling_face:.

3 Likes

It’s not a wrong way to be. Sometimes you gaze into a genserver and the genserver gazes back. It’s a descent into madness from there. Did you know erlang ships with a secret DNS library?

1 Like

DNS resolution library. What is more interesting is that there is OS monitoring library (os_mon) and metrics library (snmp).

You can also think of a GenServer mostly as a thing that allows you to serialize access to something; like a kiosk where the queued people can only be served one by one. This can be extremely important when you want to enforce this kind of access to a native lower-level resource.

Another useful thing to envision a GenServer for would be as a single worker in a pool of workers.

3 Likes

Mostly :wink: it is not terribly difficult to have genservers forward calls to someone else, do something else (aka field calls from other clients), receive the response to the forwarded message, and then send that response back to the original caller. Not a pattern I would generally recommend, unless the genserver is holding onto something important that’s stateful. Let’s say, a tcp connection that’s a black box where those async messages are sent.

I agree the name makes it sound mysterious and strange. It just means “generic server”, which is perhaps less intimidating?

It’s basically the generic skeleton for making your own little server process to do all sorts of things :slight_smile:

2 Likes

Can you folks mention some very specific examples of how you’re using genservers in your apps, please? Gotta admit that the real-world use cases (not the theoretical examples) are still a bit fuzzy for me too… Thank you :pray:

1 Like

I have an app that is a board game. A GenServer holds the state of a game. Players send actions through a Phoenix Channel and those actions are sent to my GenServer. If the action is valid, the next state of the game is computed and sent to the players channels with a broadcast.

10 Likes

I love this example!

1 Like

In a small project I use three gen_servers:

  • As a period clean-up of an ets table cache. I originally implemented the entire cache in a genserver (which is a valid use case as well) but when we needed more concurrency support I changed the cache to ets but still use the GenServer to periodically go over the cache and remove expired entries.
  • As a data storage. The app reads some secret keys from a flat file. I use Gen Server as a “database” for this. Mostly as it makes it easy to send commands to it and get it to act on the data it holds. I can send commands such as “reload” to the GenServer and it reloads the data, making sure no-one can access keys during this time (because GenServer is serialized)
  • As a supervisor for a database connection. We want to have exponential back-off and ability to run without a database altogether. If we put the database connection in a normal supervision tree and it goes down it may trigger restarts taking down the application. To get more robust control over it we use a GenServer to monitor and handle the database connection (which in itself is a GenServer but not one we wrote ourselves)

I find using GenServers is a good first step when prototyping. Anytime I need some sort of cache or process registry, periodic tasks, simple data holders I start with a gen server.

I don’t see them directly used as much in the elixir community as in erlang. The main reason I think is that elixir community relies more on dependencies rather than writing your own. For example, a phoenix project a lot is already done for you so you don’t actually need to use many of the OTP constructs directly.

4 Likes

I have been using gen_server for:

  • External process handling, where I needed to maintain the communication (with ImageMagic) for on-the-fly processing
  • Simple process queue and workers
  • Replica picker with state - LRU, MRU, RR (old project of mine that I probably should restart)
  • Sending messages via sockets without opening socket each time (in systemd library)
  • Messaging to the watchdog process in requested intervals (in systemd library)
  • Overload protection (in enough library, still WIP)
  • Listening on socket and sending the messages to the controlling process (in my gen_icmp library)
  • Owner of ETS table
  • Super sample store, where ETS would be overkill
4 Likes

Thank you all!

My understanding based on the above is that genservers are bit like external microservices - you might make api calls to various services like redis, mailer, sidekiq, translation api, weather api, etc. etc., and each of these services is rather independent, either it holds some state or not, or it just performs some task, sync or async, and if one of them dies, it doesn’t take down the whole system.

So genserver seems like a way to do this “internally” either purely within elixir world, or as a way to “wrap” some of those external services. Would that be correct?

Also I’ve heard some horror stories about taking the microservices way too far where it becomes unmanageable… I guess the supervision principles in elixir might help in this respect?

There’s one example I recall from Sasa Juric’s book, that I wanted to ask about: basically he’s building a todo lists app and each todo list is a separate genserver. I admit I haven’t read the book in all entirety and details, but is that just academic example, or does it make sense in real world too? Why would you doo that?

Thank you, and sorry if some questions sound dumb. I’d rather risk sounding dumb than risk ending up with massive misconceptions :man_shrugging:

In my mind the biggest reason why gen_servers often feel great in theory but not as useful in practise is because many projects offload most of their use cases to the webserver (cowboy) or the db (some sql). Cowboy automatically spawns one process per web request (and phoenix does so per channel-connection) so we just “get” isolation. For storage concurrency control happens at the database and with the help of transactions. So a plain old web application hardly requires additional gen_servers.

Where you can see much more gen_servers (/gen_statem) is e.g. in nerves, because here you often don’t have the isolation/concurrency control in the base system and wrapping them in an gen_server handled api does supply those properties.

5 Likes