Elixir v1.1.1 GenServer behaviour

A behaviour module for implementing the server of a client-server relation.

A GenServer is a process as any other Elixir process and it can be used to keep state, execute code asynchronously and so on. The advantage of using a generic server process (GenServer) implemented using this module is that it will have a standard set of interface functions and include functionality for tracing and error reporting. It will also fit into a supervision tree.

Example

The GenServer behaviour abstracts the common client-server interaction. Developers are only required to implement the callbacks and functionality they are interested in.

Let’s start with a code example and then explore the available callbacks. Imagine we want a GenServer that works like a stack, allowing us to push and pop items:

defmodule Stack do
  use GenServer

  # Callbacks

  def handle_call(:pop, _from, [h|t]) do
    {:reply, h, t}
  end

  def handle_cast({:push, item}, state) do
    {:noreply, [item|state]}
  end
end

# Start the server
{:ok, pid} = GenServer.start_link(Stack, [:hello])

# This is the client
GenServer.call(pid, :pop)
#=> :hello

GenServer.cast(pid, {:push, :world})
#=> :ok

GenServer.call(pid, :pop)
#=> :world

We start our Stack by calling start_link/3, passing the module with the server implementation and its initial argument (a list representing the stack containing the item :hello). We can primarily interact with the server by sending two types of messages. call messages expect a reply from the server (and are therefore synchronous) while cast messages do not.

Every time you do a GenServer.call/3, the client will send a message that must be handled by the handle_call/3 callback in the GenServer. A cast/2 message must be handled by handle_cast/2.

Callbacks

There are 6 callbacks required to be implemented in a GenServer. By adding use GenServer to your module, Elixir will automatically define all 6 callbacks for you, leaving it up to you to implement the ones you want to customize.

Name Registration

Both start_link/3 and start/3 support the GenServer to register a name on start via the :name option. Registered names are also automatically cleaned up on termination. The supported values are:

  • an atom - the GenServer is registered locally with the given name using Process.register/2.

  • {:global, term}- the GenServer is registered globally with the given term using the functions in the :global module.

  • {:via, module, term} - the GenServer is registered with the given mechanism and name. The :via option expects a module name to control the registration mechanism alongside a name which can be any term.

For example, we could start and register our Stack server locally as follows:

# Start the server and register it locally with name MyStack
{:ok, _} = GenServer.start_link(Stack, [:hello], name: MyStack)

# Now messages can be sent directly to MyStack
GenServer.call(MyStack, :pop) #=> :hello

Once the server is started, the remaining functions in this module (call/3, cast/2, and friends) will also accept an atom, or any :global or :via tuples. In general, the following formats are supported:

  • a pid
  • an atom if the server is locally registered
  • {atom, node} if the server is locally registered at another node
  • {:global, term} if the server is globally registered
  • {:via, module, name} if the server is registered through an alternative registry

Client / Server APIs

Although in the example above we have used GenServer.start_link/3 and friends to directly start and communicate with the server, most of the time we don’t call the GenServer functions directly. Instead, we wrap the calls in new functions representing the public API of the server.

Here is a better implementation of our Stack module:

defmodule Stack do
  use GenServer

  # Client

  def start_link(default) do
    GenServer.start_link(__MODULE__, default)
  end

  def push(pid, item) do
    GenServer.cast(pid, {:push, item})
  end

  def pop(pid) do
    GenServer.call(pid, :pop)
  end

  # Server (callbacks)

  def handle_call(:pop, _from, [h|t]) do
    {:reply, h, t}
  end

  def handle_call(request, from, state) do
    # Call the default implementation from GenServer
    super(request, from, state)
  end

  def handle_cast({:push, item}, state) do
    {:noreply, [item|state]}
  end

  def handle_cast(request, state) do
    super(request, state)
  end
end

In practice, it is common to have both server and client functions in the same module. If the server and/or client implementations are growing complex, you may want to have them in different modules.

Receiving custom messages

The goal of a GenServer is to abstract the “receive” loop for developers, automatically handling system messages, support code change, synchronous calls and more. Therefore, you should never call your own “receive” inside the GenServer callbacks as doing so will cause the GenServer to misbehave. If you want to receive custom messages, always receive them in handle_info/2.

Learn more

If you wish to find out more about gen servers, the Elixir Getting Started guide provides a tutorial-like introduction. The documentation and links in Erlang can also provide extra insight.

Summary

Types

Debug options supported by the start* functions

Tuple describing the client of a call request

The GenServer name

Return values of start* functions

Option values used by the start* functions

Options used by the start* functions

The server reference

Functions

Casts all servers locally registered as name at the specified nodes

Makes a synchronous call to the server and waits for its reply

Sends an asynchronous request to the server

Calls all servers locally registered as name at the specified nodes

Replies to a client

Starts a GenServer process without links (outside of a supervision tree)

Starts a GenServer process linked to the current process

Returns the pid or {name, node} of a GenServer process. Returns nil if no process is associated with the given name

Callbacks

Invoked to change the state of the GenServer when a different version of a module is loaded (hot code swapping) and the state’s term structure should be changed

Invoked to handle synchronous call/3 messages. call/3 will block until a reply is received (unless the call times out or nodes are disconnected)

Invoked to handle asynchronous cast/2 messages

Invoked to handle all other messages

Invoked when the server is started. start_link/3 (or start/3) will block until it returns

Invoked when the server is about to exit. It should do any cleanup required

Types

debug :: [:trace | :log | :statistics | {:log_to_file, Path.t}]

Debug options supported by the start* functions

from :: {pid, tag :: term}

Tuple describing the client of a call request.

pid is the pid of the caller and tag is a unique term used to identify the call.

name ::
  atom |
  {:global, term} |
  {:via, module, term}

The GenServer name

on_start ::
  {:ok, pid} |
  :ignore |
  {:error, {:already_started, pid} | term}

Return values of start* functions

option ::
  {:debug, debug} |
  {:name, name} |
  {:timeout, timeout} |
  {:spawn_opt, Process.spawn_opt}

Option values used by the start* functions

options :: [option]

Options used by the start* functions

server :: pid | name | {atom, node}

The server reference

Functions

abcast(nodes \\ nodes(), name, request)

Specs

abcast([node], name :: atom, term) :: :abcast

Casts all servers locally registered as name at the specified nodes.

The function returns immediately and ignores nodes that do not exist, or where the server name does not exist.

See multi_call/4 for more information.

call(server, request, timeout \\ 5000)

Specs

call(server, term, timeout) :: term

Makes a synchronous call to the server and waits for its reply.

The client sends the given request to the server and waits until a reply arrives or a timeout occurs. handle_call/3 will be called on the server to handle the request.

The server can be any of the values described in the Name Registration section of the module documentation.

Timeouts

The timeout is an integer greater than zero which specifies how many milliseconds to wait for a reply, or the atom :infinity to wait indefinitely. The default value is 5000. If no reply is received within the specified time, the function call fails. If the caller catches the failure and continues running, and the server is just late with the reply, it may arrive at any time later into the caller’s message queue. The caller must in this case be prepared for this and discard any such garbage messages that are two element tuples with a reference as the first element.

cast(server, request)

Specs

cast(server, term) :: :ok

Sends an asynchronous request to the server.

This function returns :ok without waiting for the destination server to handle the message. Therefore it is unknown whether the destination server successfully handled the message. If the server is an atom without an associated process an ArgumentError is raised. In all other cases the function returns :ok regardless of whether the destination server (or node) exists. Note that {name, node()} can be used when an exception is not desired if no process is locally associated with the atom name.

handle_cast/2 will be called on the server to handle the request. In case the server is on a node which is not yet connected to the caller one, the call is going to block until a connection happens. This is different than the behaviour in OTP’s :gen_server where the message is sent by another process in this case, which could cause messages to other nodes to arrive out of order.

multi_call(nodes \\ nodes(), name, request, timeout \\ :infinity)

Specs

multi_call([node], name :: atom, term, timeout) :: {replies :: [{node, term}], bad_nodes :: [node]}

Calls all servers locally registered as name at the specified nodes.

The request is first sent to every node and then we wait for the replies. This function returns a tuple containing the node and its reply as first element and all bad nodes as second element. The bad nodes is a list of nodes that either did not exist, or where a server with the given name did not exist or did not reply.

Nodes is a list of node names to which the request is sent. The default value is the list of all known nodes.

To avoid that late answers (after the timeout) pollute the caller’s message queue, a middleman process is used to do the actual calls. Late answers will then be discarded when they arrive to a terminated process.

reply(client, reply)

Specs

reply(from, term) :: :ok

Replies to a client.

This function can be used by a server to explicitly send a reply to a client that called call/3 or multi_call/4. When the reply cannot be defined in the return value of handle_call/3.

The client must be the from argument (the second argument) received in handle_call/3 callbacks. Reply is an arbitrary term which will be given back to the client as the return value of the call.

This function always returns :ok.

start(module, args, options \\ [])

Specs

start(module, any, options) :: on_start

Starts a GenServer process without links (outside of a supervision tree).

See start_link/3 for more information.

start_link(module, args, options \\ [])

Specs

start_link(module, any, options) :: on_start

Starts a GenServer process linked to the current process.

This is often used to start the GenServer as part of a supervision tree.

Once the server is started, it calls the init/1 function in the given module passing the given args to initialize it. To ensure a synchronized start-up procedure, this function does not return until init/1 has returned.

Note that a GenServer started with start_link/3 is linked to the parent process and will exit in case of crashes. The GenServer will also exit due to the :normal reasons in case it is configured to trap exits in the init/1 callback.

Options

The :name option is used for name registration as described in the module documentation. If the option :timeout option is present, the server is allowed to spend the given milliseconds initializing or it will be terminated and the start function will return {:error, :timeout}.

If the :debug option is present, the corresponding function in the :sys module will be invoked.

If the :spawn_opt option is present, its value will be passed as options to the underlying process as in Process.spawn/4.

Return values

If the server is successfully created and initialized, the function returns {:ok, pid}, where pid is the pid of the server. If a process with the specified server name already exists, the function returns {:error, {:already_started, pid}} with the pid of that process.

If the init/1 callback fails with reason, the function returns {:error, reason}. Otherwise, if it returns {:stop, reason} or :ignore, the process is terminated and the function returns {:error, reason} or :ignore, respectively.

whereis(pid)

Specs

whereis(server) :: pid | {atom, node} | nil

Returns the pid or {name, node} of a GenServer process. Returns nil if no process is associated with the given name.

For example, to lookup a server process, monitor it and send a cast:

process = GenServer.whereis(server)
monitor = Process.monitor(process)
GenServer.cast(process, :hello)

Callbacks

code_change(old_vsn, state, extra)

Specs

code_change(old_vsn, state :: term, extra :: term) ::
  {:ok, new_state :: term} |
  {:error, reason :: term} when old_vsn: term | {:down, term}

Invoked to change the state of the GenServer when a different version of a module is loaded (hot code swapping) and the state’s term structure should be changed.

old_vsn is the previous version of the module (defined by the @vsn attribute) when upgrading. When downgrading the previous version is wrapped in a 2-tuple with first element :down. state is the current state of the GenServer and extra is any extra data required to change the state.

Returning {:ok, new_state} changes the state to new_state and the code change is successful.

Returning {:error, reason} fails the code change with reason reason and the state remains as the previous state.

If code_change/3 raises the code change fails and the loop will continue with its previous state. Therefore this callback does not usually contain side effects.

handle_call(request, from, state)

Specs

handle_call(request :: term, from, state :: term) ::
  {:reply, reply, new_state} |
  {:reply, reply, new_state, timeout | :hibernate} |
  {:noreply, new_state} |
  {:noreply, new_state, timeout | :hibernate} |
  {:stop, reason, reply, new_state} |
  {:stop, reason, new_state} when reply: term, new_state: term, reason: term

Invoked to handle synchronous call/3 messages. call/3 will block until a reply is received (unless the call times out or nodes are disconnected).

request is the request message sent by a call/3, from is a 2-tuple containing the caller’s pid and a term that uniquely identifies the call, and state is the current state of the GenServer.

Returning {:reply, reply, new_state} sends the response reply to the caller and continues the loop with new state new_state.

Returning {:reply, reply, new_state, timeout} is similar to {:reply, reply, new_state} except handle_info(:timeout, new_state) will be called after timeout milliseconds if no messages are receved.

Returning {:reply, reply, new_state, :hibernate} is similar to {:reply, reply, new_state} except the process is hibernated and will continue the loop once a message is its message queue. If a message is already in the message queue this will be immediately. Hibernating a GenServer causes garbage collection and leaves a continuous heap that minimises the memory used by the process.

Hibernating should not be used aggressively as too much time could be spent garbage collecting. Normally it should only be used when a message is not expected soon and minimising the memory of the process is shown to be beneficial.

Returning {:noreply, new_state} does not send a response to the caller and continues the loop with new state new_state. The response must be sent with reply/2.

There are three main use cases for not replying using the return value:

  • To reply before returning from the callback because the response is known before calling a slow function.
  • To reply after returning from the callback because the response is not yet available.
  • To reply from another process, such as a task.

When replying from another process the GenServer should exit if the other process exits without replying as the caller will be blocking awaiting a reply.

Returning {:noreply, new_state, timeout | :hibernate} is similar to {:noreply, new_state} except a timeout or hibernation occurs as with a :reply tuple.

Returning {:stop, reason, reply, new_state} stops the loop and terminate/2 is called with reason reason and state new_state. Then the reply is sent as the response to call and the process exits with reason reason.

Returning {:stop, reason, new_state} is similar to {:stop, reason, reply, new_state} except a reply is not sent.

handle_cast(request, state)

Specs

handle_cast(request :: term, state :: term) ::
  {:noreply, new_state} |
  {:noreply, new_state, timeout | :hibernate} |
  {:stop, reason :: term, new_state} when new_state: term

Invoked to handle asynchronous cast/2 messages.

request is the request message sent by a cast/2 and state is the current state of the GenServer.

Returning {:noreply, new_state} continues the loop with new state new_state.

Returning {:noreply, new_state, timeout} is similar to {:noreply, reply, new_state} except handle_info(:timeout, new_state) will be called after timeout milliseconds if no messages are received.

Returning {:noreply, new_state, :hibernate} is similar to {:noreply, new_state} except the process is hibernated before continuing the loop. See handle_call/3 for more information.

Returning {:stop, reason, new_state} stops the loop and terminate/2 is called with the reason reason and state new_state. The process exits with reason reason.

handle_info(msg, state)

Specs

handle_info(msg :: :timeout | term, state :: term) ::
  {:noreply, new_state} |
  {:noreply, new_state, timeout | :hibernate} |
  {:stop, reason :: term, new_state} when new_state: term

Invoked to handle all other messages.

msg is the message and state is the current state of the GenServer. When a timeout occurs the message is :timeout.

Return values are the same as handle_cast/2.

init(args)

Specs

init(args :: term) ::
  {:ok, state} |
  {:ok, state, timeout | :hibernate} |
  :ignore |
  {:stop, reason :: any} when state: any

Invoked when the server is started. start_link/3 (or start/3) will block until it returns.

args is the argument term (second argument) passed to start_link/3.

Returning {:ok, state} will cause start_link/3 to return {:ok, pid} and the process to enter its loop.

Returning {:ok, state, timeout} is similar to {:ok, state} except handle_info(:timeout, state) will be called after timeout milliseconds if no messages are received within the timeout.

Returning {:ok, state, :hibernate} is similar to {:ok, state} except the process is hibernated before entering the loop. See handle_call/3 for more information on hibernation.

Returning :ignore will cause start_link/3 to return :ignore and the process will exit normally without entering the loop or calling terminate/2. If used when part of a supervision tree the parent supervisor will not fail to start nor immediately try to restart the GenServer. The remainder of the supervision tree will be (re)started and so the GenServer should not be required by other processes. It can be started later with Supervisor.restart_child/2 as the child specification is saved in the parent supervisor. The main use cases for this are:

  • The GenServer is disabled by configuration but might be enabled later.
  • An error occured and it will be handled by a different mechanism than the Supervisor. Likely this approach involves calling Supervisor.restart_child/2 after a delay to attempt a restart.

Returning {:stop, reason} will cause start_link/3 to return {:error, reason} and the process to exit with reason reason without entering the loop or calling terminate/2.

terminate(reason, state)

Specs

terminate(reason, state :: term) :: term when reason: :normal | :shutdown | {:shutdown, term} | term

Invoked when the server is about to exit. It should do any cleanup required.

reason is exit reason and state is the current state of the GenServer. The return value is ignored.

terminate/2 is called if a callback (except init/1) returns a :stop tuple, raises, calls Kernel.exit/1 or returns an invalid value. It may also be called if the GenServer traps exits using Process.flag/2 and the parent process sends an exit signal.

If part of a supervision tree a GenServer’s Supervisor will send an exit signal when shutting it down. The exit signal is based on the shutdown strategy in the child’s specification. If it is :brutal_kill the GenServer is killed and so terminate/2 is not called. However if it is a timeout the Supervisor will send the exit signal :shutdown and the GenServer will have the duration of the timeout to call terminate/2 - if the process is still alive after the timeout it is killed.

If the GenServer receives an exit signal (that is not :normal) from any process when it is not trapping exits it will exit abruptly with the same reason and so not call terminate/2. Note that a process does NOT trap exits by default and an exit signal is sent when a linked process exits or its node is disconnected.

Therefore it is not guaranteed that terminate/2 is called when a GenServer exits. For such reasons, we usually recommend important clean-up rules to happen in separated processes either by use of monitoring or by links themselves. For example if the GenServer controls a port (e.g. :gen_tcp.socket) or File.io_device, they will be closed on receiving a GenServer’s exit signal and do not need to be closed in terminate/2.

If reason is not :normal, :shutdown nor {:shutdown, term} an error is logged.