Introduction
Elixir describes itself as “a dynamic, functional programming language designed for building scalable and maintainable applications.” Although it’s a relative newcomer, Elixir is built on top of the functional programming language Erlang. Elixir is capable of using any Erlang library, and is ideal for use cases such as web development and distributed and low-latency systems.
The power, dynamism, and feature-rich toolset of Elixir have made it somewhat of a “cult classic” among programmers. According to Stack Overflow’s 2019 survey, Elixir ranks in the top 10 of software developers’ “most loved” languages, ahead of popular alternatives such as Java, C++, and JavaScript.
One of the essential concepts in Elixir is the GenServer (short for “generic server”). But what is a GenServer in Elixir exactly? If you’ve ever wondered how to use Elixir GenServers, this tutorial is for you.
Below, we’ll go over everything you need to know about GenServers in Elixir: GenServer examples and use cases, how to implement a GenServer in Elixir, as well as GenServer monitoring and debugging.
What is a GenServer?
According to the Elixir project, a GenServer is “a behavior module for implementing the server of a client-server relation.” GenServer processes are used for multiple purposes, including storing states and running code asynchronously.
Elixir is a functional programming language frequently used for parallel and distributed systems. The core unit of Elixir is the process. Each process is highly lightweight and efficient, runs concurrently to other processes, and communicates with them via message passing.
GenServers, too, are processes in Elixir. By using GenServers in Elixir, you can elegantly perform asynchronous communication, without having to write low-level code that deals with edge cases and error handling. GenServers come equipped with a set of standardized interface functions, as well as tracing and error reporting features, that help you get your servers up and running more quickly in Elixir.
Example of an Elixir GenServer
There are many potential GenServer use cases in Elixir. GenServers are an abstraction of the server in a client-server relationship, so any code that makes sense to run on a server can run on a GenServer as well.
Some of the possible examples of using GenServers in Elixir are:
- Handling synchronous and asynchronous messages.
- Handling system messages (e.g., event notifications and alerts).
- Modeling specific runtime properties such as mutable state, concurrency, and failures.
That said, when should you not use GenServers in Elixir? The purpose of a GenServer is to perform computations that should run in an additional process. Using a GenServer to organize your code is computationally inefficient and not necessary; instead, use modules and functions for code organization in Elixir.
How to Implement a GenServer in Elixir
Implementing a GenServer in Elixir requires you to write two separate parts: the client API and the server callbacks.
- The client API (application programming interface) is a set of functions that the client uses to communicate with the GenServer.
- The server callbacks are actions that the GenServer can take in response to a message from the client.
These two parts can be implemented together within a single module, or they can be implemented separately within a client module and a server module. The Elixir project provides the sample source code below for how to implement GenServer server callbacks. In this example, the GenServer is used to store key-value pairs in buckets:
defmodule KV.Registry do
use GenServer
## Missing Client API - will add this later
## Defining GenServer Callbacks
@impl true
def init(:ok) do
{:ok, %{}}
end
@impl true
def handle_call({:lookup, name}, _from, names) do
{:reply, Map.fetch(names, name), names}
end
@impl true
def handle_cast({:create, name}, names) do
if Map.has_key?(names, name) do
{:noreply, names}
else
{:ok, bucket} = KV.Bucket.start_link([])
{:noreply, Map.put(names, name, bucket)}
end
end
end
Notice that we have two server callbacks in this example: handle_call and handle_cast.
- A call is a synchronous message from the client, which will wait until it receives a message from the server or until the request times out.
- A cast is an asynchronous message from the client, which means that the server may not immediately respond to the request; also, the client may not know whether the server has successfully received the message.
Also, notice the init function in the source code above. This function is required when defining server callbacks in GenServer and accepts an arbitrary parameter. Here, the parameter is a placeholder argument: ok, but it could be more complex depending on the function of the GenServer.
Let’s now look at how to implement the GenServer client API. Again, the Elixir project provides the below example code:
@doc """
Starts the registry.
"""
def start_link(opts) do
GenServer.start_link(__MODULE__, :ok, opts)
end
@doc """
Looks up the bucket pid for `name` stored in `server`.
Returns `{:ok, pid}` if the bucket exists, `:error` otherwise.
"""
def lookup(server, name) do
GenServer.call(server, {:lookup, name})
end
@doc """
Ensures there is a bucket associated with the given `name` in `server`.
"""
def create(server, name) do
GenServer.cast(server, {:create, name})
end
The lookup and create functions are part of the client API, which is used to communicate with and request a response from the GenServer. (Notice that lookup is a call, while create is a cast.)
For a full description of GenServer functionality and what GenServers can do in Elixir, check out the:gen_server module documentation.
GenServer Monitoring in Elixir
You’ve used the tutorial above to spin up your first GenServer in Elixir. But how can you ensure that this GenServer will continue to run well in production?
Like all servers, GenServers in Elixir need to be monitored continuously to ensure that they are running at optimal performance. There are multiple ways that you can monitor GenServers. For example, you can create another Elixir process to monitor the GenServer, or you can use third-party Elixir monitoring software.
The following code snippet demonstrates a simple way to detect whether, given a process ID, the process matching that PID is down:
ref = Process.monitor(pid)
receive do
{:DOWN, ^ref, :process, ^pid, :normal} ->
IO.puts "Normal exit from #{inspect pid}"
{:DOWN, ^ref, :process, ^pid, msg} ->
IO.puts "Received :DOWN from #{inspect pid}"
IO.inspect msg
end
Monitoring GenServers can be a tricky task, which is why many Elixir developers use third-party tools like Scout APM. These Elixir application monitoring tools have been custom-built for working with Elixir programs. In particular, Scout is the only Elixir application monitoring platform that can help identify and solve problems such as N+1 database queries and performance abnormalities.
Debugging GenServers in Elixir
Not only do you need to monitor GenServers in Elixir, but you also need to debug them if you encounter crashes and performance issues. To debug a GenServer in Elixir, you can use the: sys module. This module contains a variety of tools and functions to help users examine processes and trace events during program runtime.
Some of the most critical functions in Elixir’s: sys modules are described below:
- get_state: Given a GenServer identifier (e.g., a process ID), this function returns the current state of the GenServer.
- get_status: Given a GenServer identifier, this function returns the GenServer’s current status (its state plus some. This includes the process dictionary, the system state (either running or suspended), the PID of the parent process, the state of the debugger, and the state of the callback module.
- Trace: This function prints all system events to standard output, including the flow of messages to and from the GenServer.
- Suspend: This function suspends a process so that it will only respond to system messages, not messages from other processes.
- Resume: This function resumes a process that has been suspended.
To learn more about debugging Elixir programs, including how to use the built-in graphical debugger, check out this official Elixir debugging tutorial.
Conclusion
GenServers are a foundational part of the Elixir programming language. They can function as an independent abstraction of a server in the client-server relationship. Also, they can act as a building block for more complex systems and components within the Elixir ecosystem.
No matter how you plan to use them, GenServers require careful, consistent monitoring to keep your software at peak performance. Are you looking for the best way to monitor GenServers in Elixir? Scout APM can help. Our automated, easy-to-use application monitoring platform lets you spend more time on what’s important—developing software—and less time on solving frustrating performance issues.
Scout can be easily installed using Elixir’s Mix tool, and also supports popular Elixir libraries and frameworks such as Phoenix, Ecto, and Slime. Get in touch with our team for a chat about your business needs and objectives, or sign up for a 14-day free trial of the Scout application monitoring platform.