# Understanding GenServers

In this post, we'll be demystifying GenServers. We'll discuss its anatomy and then later roll out our own version of GenServer from the ground up.

*To fully grasp this article, you must at least know the basics of Elixir/Erlang processes.*

# GenServer in Action

If you already know how to use GenServers, feel free to skip this section and jump straight to **Anatomy of a GenServer**.

GenServer is a behavior that facilitates client-server interaction. It abstracts away all the nitty-gritty details when dealing with interprocess communications.

In this example, we'll build an item accumulator. It would be an Elixir process that accepts an item and stores it in a list. This is how it would look like if we were to use the process primitive:

```elixir
iex(1)> pid = spawn(fn ->
...(1)>     receive do
...(1)>       {sender, acc, item} -> send(sender, [item | acc])
...(1)>     end
...(1)>   end)
#PID<0.229.0>
iex(2)> send pid, {self, [], "apple"}
{#PID<0.169.0>, [], "apple"}
iex(3)> flush
["apple"]
```

As you may have noticed, we would need to pass a couple of things around. We would also need to make sure that it's a long-living process (we'll discuss more about this later). It's easy to get lost within this code. This is why GenServer behavior exists.

Let's rewrite this using GenServer. To start, we need to define a module that implements the GenServer *behaviour*.

```elixir
defmodule ItemAccumulator do
  use GenServer
end
```

First, it should be able to set an initial state, in our case, an initial accumulator. To do this, we need to implement the `init/1` callback.

```elixir
@impl true
def init(acc) do
  {:ok, acc}
end
```

Our `ItemAccumulator` will have two functionalities. We need to have a function that would store the item and another function to return the items. When storing an item, we don't need a response; hence, we can use `GenServer.cast/2`. On our GenServer module, we then need to handle the `cast` request using the `handle_cast/2` callback.

```elixir
@impl true
def handle_cast({:add, item}, acc) do
  {:noreply, [item | acc]}
end
```

On the other hand, when we ask for the list of items in the accumulator, we need to wait for a response from the server. Because of this, we need to use `GenServer.call/3`. On our GenServer module, we would need to implement the `handle_call/3` callback.

Lastly, let's also show the accumulated items before the process exits.

```elixir
@impl true
def terminate(_reason, acc) do
  IO.puts "Last state: #{inspect(acc)}"
end
```

This is how our GenServer module should look like:

```elixir
defmodule ItemAccumulator do
  use GenServer

  @impl true
  def init(acc) do
    {:ok, acc}
  end

  @impl true
  def handle_call(:list_items, from, acc) do
    {:reply, acc, acc}
  end

  @impl true
  def handle_cast({:add, item}, acc) do
    {:noreply, [item | acc]}
  end

  @impl true
  def terminate(_reason, acc) do
    IO.puts "Last state: #{inspect(acc)}"
  end
end
```

Let us test this out.

```elixir
iex(1)> GenServer.start_link(ItemAccumulator, [], name: ItemAccumulator)
{:ok, #PID<0.165.0>}
```

The `start_link/3` function starts the GenServer process. Giving it a `name` would register the process and would make it easier for us to locate and use it.

```elixir
iex(2)> GenServer.cast(ItemAccumulator, {:add, "Apples"})
:ok
iex(3)> GenServer.cast(ItemAccumulator, {:add, "Oranges"})
:ok
iex(4)> GenServer.call(ItemAccumulator, :list_items)
["Oranges", "Apples"]
iex(5)> GenServer.stop(ItemAccumulator)
Last state: ["Oranges", "Apples"]
:ok
```

Since we have a working GenServer, we can now stop here or make the interface more developer-friendly. We can do that by abstracting the implementation details away from the client. They don't always need to know the implementation details, in our case, that we're using GenServers.

```elixir
defmodule ItemAccumulator do
  use GenServer

  def start_link(state) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def add(item) do
    GenServer.cast(__MODULE__, {:add, item})
  end

  def list_items(), do: GenServer.call(__MODULE__, :list_items)

  ...
  # callbacks
end
```

Let's try it out in the shell.

```elixir
iex(1)> ItemAccumulator.start_link([])
{:ok, #PID<0.155.0>}
iex(2)> ItemAccumulator.add("Apples")
:ok
iex(3)> ItemAccumulator.add("Oranges")
:ok
iex(4)> ItemAccumulator.list()
["Oranges", "Apples"]
```

The interface now looks cleaner and just focuses on the functionalities instead of implementation details.

# Anatomy of a GenServer

A GenServer is just a regular Elixir/Erlang process that is *stuck* in a loop. Don't believe me? Here's what the observer shows when you're running a GenServer process.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1701265823838/9a884161-6b24-422f-8e98-0ecf314f635f.jpeg align="center")

It's an Erlang process that waits for a message, acts on the message, then loops and waits for a message again.

Take a look at the snippet below.

```elixir
defmodule HealthCheck do
  def loop() do
    receive do
      {pid, :ping} ->
        send(pid,:pong)
        loop()
      :exit -> 
        Process.exit(self(), :normal)
    end
  end
end
```

We define a module called `HealthCheck` that has a function that just responds to a ping message and then loops. Let's use this module to build our first dumb version of our generic server.

```elixir
iex(1)> pid = spawn(&HealthCheck.loop/0)
#PID<0.155.0>
```

We now have a running generic server. To confirm that it's indeed alive, verify it using `Process.alive?/1`.

```elixir
iex(2)> Process.alive?(pid)
true
```

Let's do a ping check and see if our server responds.

```elixir
iex(3)> send(pid, {self(), :ping})
{#PID<0.151.0>, :ping}
iex(4)> Process.info(self(), :messages)
{:messages, [:pong]}
```

Now confirm if our generic server is still alive.

```elixir
iex(5)> Process.alive?(pid)
true
```

We just built a long-running process. This is GenServer under the hood, with some quirks sprinkled on top of it.

Now let's build our own version of a GenServer!

# BasicServer

## Requirements

In this mini-project, we aim to replicate the following functionalities of a GenServer:

* `init` - We should be able to set the initial state.
    
* `call` - We should be able to make synchronous calls to the server and wait for a reply.
    
* `cast` - We should be able to send fire-and-forget calls to the server.
    
* `stop` - We should be able to stop the server.
    

GenServers have other functionalities like `multicast`, `multicall`, etc but we'll only focus on the functionalities enumerated above.

## `__using__` Macro

The `__using__` macro allows us to inject code into another module. We need this so that we can set the behavior for the server module and implement default callbacks.

```elixir
defmodule BasicServer do
  @callback init(state :: term()) :: {:ok, term()} | {:stop, term()}

  defmacro __using__(_opts) do
    quote do
      @behaviour BasicServer
    end
  end
end
```

## Setting Initial State

Setting the initial state of a GenServer is done via the `init/1` callback. A module that is using our BasicServer must have an `init/1` callback defined; otherwise, we raise an exception.

```elixir
...
defmacro __using__(_opts) do
  quote do
    @behaviour BasicServer

    def init(_state) do
      raise "you forgot to implement the init callback!"
    end

    defoverridable init: 1
  end
end
...
```

## Starting the Server

Now that we're enforcing the presence of an `init/1` callback, we can proceed with building our first function which is starting the BasicServer.

```elixir
defmodule BasicServer do
  @callback init(state :: term()) :: {:ok, term()} | {:stop, term()}

  ...

  def start_link(callback_mod, init_state, opts \\ []) do
    pid =
      # links to the parent process
      spawn_link(fn ->
        {:ok, state} = apply(callback_mod, :init, [init_state])

        loop(callback_mod, state)
      end)

    # registers the process
    if opts[:name], do: Process.register(pid, opts[:name])

    {:ok, pid}
  end

  defp loop(callback, state) do
    # this receive-block will do the heavy-lifting later
    receive do
      _ -> loop(callback, state)
    end
  end

  ...
end
```

## Synchronous Calls

In this section, we will be building the `call` part of GenServers. `call` is an asynchronous operation. It will not proceed with other actions until it receives a reply or it times out. How are we going to build this? `call` will send a message to our server process which is in a suspended state and just waiting for a message to arrive. Once our server process receives the message, it will call the callback module's `handle_call/3` function, passing in the message and current state. The calling process would then enter a suspended state waiting for a reply.

Let's create the `call/2` function.

```elixir
...
def call(pid, message) do
  # we need to mark the message as a `:call` to pattern-match later
  # in the receive-loop
  send(pid, {:call, self(), message})

  # since `call` is a synchronous operation, we need to wait for
  # a response from the server
  receive do
    {:reply, reply} ->
      reply
  end
end
...
```

Now, we modify the receive-block inside the looping function to handle the `call` message and call the appropriate callback function.

```elixir
...
defp loop(callback, state) do
  receive do
    {:call, caller_pid, message} ->
      # for this example, we'll only handle the
      # `{:reply, reply, state}` response.
      {:reply, reply, new_state} = apply(callback, :handle_call, [message, caller_pid, state])
      send(caller_pid, {:reply, reply})
      loop(callback, new_state)
  end
end
...
```

## Asynchronous Casts

Let's deal with the fire-and-forget requests. This is very similar to the `call` function, except that we don't need to send a reply back to the calling process.

```elixir
...
def cast(pid, message) do
  # we don't need the caller_pid anymore
  # since we don't need to send a message back
  send(pid, {:cast, message})
end
...
```

Handling the `cast` message is also simpler. We just need to call the callback function.

```elixir
...
defp loop(callback, state) do
  receive do
    {:call, caller_pid, message} ->
      {:reply, reply, new_state} = apply(callback, :handle_call, [message, caller_pid, state])
      send(caller_pid, {:reply, reply})
      loop(callback, new_state)

    {:cast, message} ->
      {:noreply, new_state} = apply(callback, :handle_cast, [message, state])
      loop(callback, new_state)
  end
end
...
```

## Hey Server, stop!

Finally, the last remaining function, `stop`. This function would just tell our server to exit. Well, it needs to do some cleanup first before exiting. Let's start by implementing the stop function.

```elixir
...
def stop(server, reason) do
  send(server, {:stop, reason})

  :ok
end
...
```

Hmm... why do we need to send a message to the server? Remember, the server is in a suspended state waiting for a message. Sure, we can just `kill` the server process, but that's not a graceful exit. Once the server receives the exit message, it executes the `terminate/2` callback and then escapes the loop; thus, terminating the server process.

```elixir
...
defp loop(callback, state) do
  receive do
    {:call, caller_pid, message} ->
      {:reply, reply, new_state} = apply(callback, :handle_call, [message, caller_pid, state])
      send(caller_pid, {:reply, reply})
      loop(callback, new_state)

    {:cast, message} ->
      {:noreply, new_state} = apply(callback, :handle_cast, [message, state])
      loop(callback, new_state)

    {:stop, reason} ->
      apply(callback, :terminate, [reason, state])
  end
end
...
```

## Testing Our Basic Server

Here's the full code of our GenServer clone, **BasicServer**.

```elixir
defmodule BasicServer do
  @callback init(state :: term()) :: {:ok, term()} | {:stop, term()}
  @callback handle_call(msg :: term(), from :: pid(), state :: term()) ::
              {:reply, reply :: term(), state :: term()}
  @callback handle_cast(msg :: term(), state :: term()) :: {:noreply, state :: term()}
  @callback terminate(reason :: atom(), state :: term()) :: :ok
  
  defmacro __using__(_opts) do
    quote do
      @behaviour BasicServer

      def init(_state) do
        raise "you forgot to implement the init callback!"
      end

      defoverridable init: 1
    end
  end

  def start_link(callback_mod, init_state, opts \\ []) do
    pid =
      spawn_link(fn ->
        {:ok, state} = apply(callback_mod, :init, [init_state])

        loop(callback_mod, state)
      end)

    if opts[:name], do: Process.register(pid, opts[:name])

    {:ok, pid}
  end

  def call(pid, message) do
    send(pid, {:call, self(), message})

    receive do
      {:reply, reply} ->
        reply
    end
  end

  def cast(server, message) do
    send(server, {:cast, message})

    :ok
  end

  def stop(server, reason) do
    send(server, {:stop, reason})

    :ok
  end

  defp loop(callback, state) do
    receive do
      {:call, caller_pid, message} ->
        {:reply, reply, new_state} = apply(callback, :handle_call, [message, caller_pid, state])
        send(caller_pid, {:reply, reply})
        loop(callback, new_state)

      {:cast, message} ->
        {:noreply, new_state} = apply(callback, :handle_cast, [message, state])
        loop(callback, new_state)

      {:stop, reason} ->
        apply(callback, :terminate, [reason, state])
    end
  end
end
```

We'll reuse the `ItemAcc` we wrote before but instead of using GenServer, we use BasicServer.

```elixir
defmodule ItemAcc do
  use BasicServer

  def start_link(state) do
    BasicServer.start_link(__MODULE__, state, name: __MODULE__)
  end

  def add(item) do
    BasicServer.cast(__MODULE__, {:add, item})
  end

  def list_items(), do: BasicServer.call(__MODULE__, :list_items)

  def stop(), do: BasicServer.stop(__MODULE__, :normal)

  @impl true
  def init(state), do: {:ok, state}

  @impl true
  def handle_call(:list_items, _from, acc) do
    {:reply, acc, acc}
  end

  @impl true
  def handle_cast({:add, item}, acc) do
    {:noreply, [item | acc]}
  end

  @impl true
  def terminate(_reason, state) do
    IO.puts "Terminating... Last state: #{inspect(state)}"
  end
end
```

```elixir
iex(1)> ItemAcc.start_link([])
{:ok, #PID<0.155.0>}
iex(2)> ItemAcc.add("Apples")
:ok
iex(3)> ItemAcc.add("Oranges")
:ok
iex(4)> ItemAcc.list_items()
["Oranges", "Apples"]
iex(5)> ItemAcc.stop()
Terminating... Last state: ["Oranges", "Apples"]
:ok
```

We are now done implementing the core functionalities of a GenServer. I think it's important to highlight that **you should not build your own GenServer**. Elixir's GenServers are battle-tested already. You most likely don't need to reinvent the wheel. We only did it for educational purposes. If you need client-server communication, then just use the already-built GenServer.

If you enjoyed this insightful post and would like to receive more content like this, I warmly invite you to subscribe to my newsletter. Your support is truly appreciated! See you on the next one!
