GenServer n00b question

I’m new to Elixir and learning GenServer and after running the following code:

defmodule Sequence.Server do
  use GenServer

  def init(args) do
    {:ok, args}
  end

  def handle_call(:next_number, _from, current_number) do
    { :reply, current_number, current_number + 1 }
  end

  def handle_call({:set_number, new_number}, _from, _current_number) do
    { :reply, new_number, new_number }
  end

  def handle_call(:helloworld, _from, _state) do
    { :reply, "Hello, World!", []}
  end
end

…and testing it:

output

iex(8)> { :ok, pid } = GenServer.start_link(Sequence.Server, 20)
{:ok, #PID<0.253.0>}
iex(9)> GenServer.call(pid, :next_number)
20
iex(10)> GenServer.call(pid, :next_number)
21
iex(11)> GenServer.call(pid, :next_number)
22
iex(12)> GenServer.call(pid, {:set_number, 555})
555
iex(13)> GenServer.call(pid, :next_number)
555
iex(14)> GenServer.call(pid, :next_number)
556

How did Elixir/GenServer know to update (or at least use) the same value for :set_number and :next_number? Coming from a OO background, this would make sense if there was a global variable that they both referenced but that is not the case here.

Which also leads me to another question of how would I isolate it?

The genserver process (identified by pid) you started with #start_link stores your state (current_number) internally.
So its kind of like an object in terms of storing state. Another example of process with state is Agent.

If you start another genserver you wont have access to state of first one.

I think it might be clearer, if you look at how something similar would be achieved in a simple process:

defmodule Sequence.Server do
  def loop(state) do
    receive do
      {:next_number, from} ->
        send(from, state)
        loop(state + 1)
      {:set_number, new_number, from} ->
        send(from, new_number)
        loop(new_number)
    end
  end
end
iex(2)> pid = spawn_link(Sequence.Server, :loop, [20])
#PID<0.109.0>
iex(3)> send(pid, {:next_number, self()})
{:next_number, #PID<0.88.0>}
iex(4)> flush
20
:ok
iex(5)> send(pid, {:next_number, self()})
{:next_number, #PID<0.88.0>}
iex(6)> flush
21
:ok
iex(7)> send(pid, {:next_number, self()})
{:next_number, #PID<0.88.0>}
iex(8)> flush
22
:ok
iex(9)> send(pid, {:set_number, 555, self()})
{:set_number, 555, #PID<0.88.0>}
iex(10)> flush
555
:ok
iex(11)> send(pid, {:next_number, self()})
{:next_number, #PID<0.88.0>}
iex(12)> flush
555
:ok
iex(13)> send(pid, {:next_number, self()})
{:next_number, #PID<0.88.0>}
iex(14)> flush
556
:ok

The internal implementation of the GenServer will do something very similar to this threading the state variable throughout the recursive calls to a loop function.

5 Likes

@michalmuskala, are you saying that GenServer’s implementation of handle_call (or cast, etc.) is nothing more than cases within a receive block?

So is it one state per GenServer process? For example, if I want to keep track of a numeric value (like the example above), then that would be one process. However, if I want to track a Map, would I have to do that in another process?

You can define a new data structure composed of a map and a numeric value and save it as the state of the GenServer. Or, the numeric value could be one of the values in your map. Below is an interesting article about how to build a complex data structure in Elixir. This could be stored as the state of a GenServer.

Effectively, yes.

Technically, it layers support for several extra layers of functionality on top: this is where you get features like monitoring, supervision, hot code upgrades, and those other goodies. That’s why we like to refer to erlang/OTP as a unit: the erlang-language-only approach to make a server might be the looped receive block above, but with very little buy-in you can write erlang/Elixir code that fits into the OTP framework that comes with the distribution, to make far more robust and powerful distributed applications with minimal boilerplate.


The one piece of state you get to use inside a GenServer can be any term, including a compound data structure. The data-shape you use for your server’s is entirely up to you, OTP enforces no expectations upon it so you can come up with whatever convention you want for your use-case.

So in your example, if you wanted to co-locate both a numeric piece of state with a map in the same server process, you could decide to have your state be shaped as a two-tuple of {number, map} and pattern match them apart to use them both in your callback functions. You could also use a list or map or struct or any other compound data-structure as a container for your multiple pieces of state, whichever is most appropriate for your scenario.

1 Like

Okay, I think it makes sense to me now. For simplicity’s sake to wrap this up based on my original question, you can only keep track of one data element per GenServer process. However, that data element can contain also contain one or more data elements, e.g. Tuple, List, Map, Struct, etc., which can contain additional data types in themselves.

Keep in mind, in OO terms, this is weird :wink:

1 Like

It’s more a matter of perspective.

Even in OO you need a reference to interact with an object. Your comment seems to suggest that you have been relying heavily on the global namespace to implicitly hold the references that you need.

Many (more) testable object designs will often start with a more tree-like organization of references in as far as they start with a single root object that holds the references of the most important parts of the application which in turn reference the next level of important objects and so on (in many cases this approach leads to the use of an dependency injection manager).

The point being is that the “one data element” is simply the root of all the data that the process manages and as such it is explicitly managed and “updated” via recursion.

4 Likes