I’ve been using GenServers in many apps, but I found out in the Slack I still don’t know what it can be used for and how flexible it is.
So I had an idea to collect examples of GenServers here, in the forum. With enough interest I could turn this into a separate website, like on Hexdocs perhaps (with tests and everything). I realize many GenServers are already usable libraries. But I like the philosophy of phx.gen.auth, where it generates you code to use, rather than hide the implementation.
Or does such a place with snippets exist?
I’ll start with a simple loop that simply does something after an interval.
Warning - if you actually want to change state you’d better keep that in another GenServer so a logic crash wont lose state.
defmodule MyApp.SimpleLoopWorker do
def start_link(state) do
def init(state) do
Process.send_after(self(), :run, 1000)
def handle_info(:run, state) do
# Do work
Process.send_after(self(), :run, 60 * 1000)
Request: an outward rate limiter. used to access an external API with a rate limit. specially good is an implementation with multiple possible API keys, where each key has an independent rate counter. I’ll post this when i get mine ready, but would love to see yours.
GenServers excel at is to serialize access to something they hold in their state. This is due to the nature of the BEAM VM where processes can only process one message at a time and their order of receiving is guaranteed to be the same as the chronological order of sending them.
Another thing they are very good at is be persistent workers that are part of your app’s supervision tree (which is a super-set of the above scenario).
A good example (that you kind of alluded to) for both points is one
GenServer per external API that you might need in your app. A single
GenServer will only ever send a single request to the external API and it can also throttle access to abide by a total budget of requests per time period.
Here is one popular example that illustrates many of the benefits: Papercups | Learning Elixir's GenServer with a real-world example
Also worth reading Sasa Jurcic’s book.
@josefrichter yeah i’ve read that post and it’s a good one.
@dimitarvp thanks for explaining, i was hoping to collect a library of actualy GenServers.
If you are using a Genserver per endpoint to serialize external API calls, you can just do a progressive backoff with
Process.sleep/1 when you get a
429 Too Many Requests response. Of course you can go fancier but this simple approach has worked well for me.
This is a bad pattern for triggering on an interval. You should use the :timer module instead. Why? Because there are some real risks that you wind up with a timing you don’t expect, or no triggering at all.
Care to elaborate more? The genserver’s sole purpose is to do the remote api call, there is nothing else going on and nothing else to do when the remote side tell me that I talked too much.