Strange GenServer - why two different pids?

I wrote a GenServer.

# client api
  def start_link(question_list) do
    self() |> yellow
    GenServer.start_link(__MODULE__, question_list)
  end

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

  def stop(pid) do
    GenServer.stop(pid, :finished_quiz)
  end

  # server api

  def init(qs_list \\ []) do
    {:ok, qs_list}
  end

  def handle_call(:next, _from, []) do
    {:reply, :terminate, []}
  end

  def handle_call(:next, _from, [question | remaining_qs_list]) do
    {:reply, question, remaining_qs_list}
  end

  def terminate(:finished_quiz, _qs_list) do
    Logger.info("QuizSend Genserver stopped.")
    "finished quiz, exiting" |> orange
  end

yellow and orange are IO.inspect with colors.

But strange part is

  1. self() in start_link prints #PID<0.902.0>
  2. when I call this genserver like QuestionsServer.start_link() |> yellow the reply is {:ok, #PID<0.906.0>}

Why two different pids? It must be one GenServer, right?

Additional info.


iex(3)> Process.alive?(:c.pid(0,906,0))
true
iex(4)> Process.alive?(:c.pid(0,902,0))
false

The self() in start_link/1 runs in the context of the process calling start_link and not in the newly created GenServer (since it wasn’t created yet).

So it prints the pid of the current process and returns the pid of the created GenServer

2 Likes

self() – which process is it? The one which called the GenServer?

Can I get the pid of the GenServer from inside GenServer? say, init function?

Yes, your start_link/1 is a function like any other. It is like calling:

def new_self do
  self()
end

Yes, init/1 is called in the GenServer process.

1 Like

Why two different pids? It must be one GenServer, right?

No. You can start as many GenServers of the same type as you want, they use the same code, but they are not the same process.

Your example reminded me of my first OOP lessons in University: you have a blue print for a car, that’s always the same, but when the cars come out of the factory, they all look the same - but they are not the same object. You can get into one car and drive, that does not influence the blueprint, or any of the other cars.

Same in this case. You can start GenServers all over the place, they will all have different process IDs, and different states, because they all have different processes.

2 Likes

It’s important to understand that start_link is not part of the GenServer behaviour. It is just a convention to name it that way. It is part of the client functions of your module, like next and stop so it executes within the caller’s process. Only the GenServer callbacks, as specified in the behaviour, execute within the GenServer process itself.

5 Likes

in my case, the command to start genserver was run only once.
So there must be only one genserver.

also, the self() in start_link dies after calling GenServer.start_link

So, self() is the caller;s pid.

I verified it. and this is indeed the case.

Thank you so much!

I would bet that the self() call is not affected. But you need to return the return value of GenServer.start_link from the start_link function. If you want to run self() after it, you need to do something like

def start_link(question_list) do
  ret = GenServer.start_link(__MODULE__, question_list)
  self() |> yellow
  ret
end