Why GenServer.reply is not received by the caller?

Hello! I created the minimum code possible to explain my case:

defmodule DelayedReply do
  use GenServer

  def start_link(), do: GenServer.start_link(__MODULE__, [])
  def my_call(pid), do: GenServer.call(pid, :call, 100)

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

  @impl true
  def handle_call(:call, caller, state) do
    Process.send_after(self(), {:call, caller}, 500)
    {:noreply, state}
  end

  @impl true
  def handle_info({:call, caller}, state) do
    {pid, _ref} = caller
    send(pid, :custom_message)
    GenServer.reply(caller, :ok)
    {:noreply, state}
  end
end

test "test" do
  assert {:ok, server} = DelayedReply.start_link()

  try do
    DelayedReply.my_call(server)
  catch
    :exit, {:timeout, _} -> :ok
  end

  assert_receive :custom_message, 5000
  # Never received the GenServer.reply message
  assert_receive _msg, 5000
end

If the caller doesn’t die because there is a try/catch block; why the GenServer.reply is not received?

A similar example with the same result:

defmodule SleepReply do
 use GenServer

 def start_link(), do: GenServer.start_link(__MODULE__, [])
 def my_call(pid), do: GenServer.call(pid, :call, 100)

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

 @impl true
 def handle_call(:call, caller, state) do
   Process.sleep(200)
   {pid, _} = caller
   send(pid, :custom_message)
   {:reply, :ok, state}
 end
end

test "test 2" do
 assert {:ok, server} = SleepReply.start_link()

 try do
   SleepReply.my_call(server)
 catch
   :exit, {:timeout, _} -> :ok
 end

 assert_receive :custom_message, 1000
 assert_receive _msg, 1000
end

How does the GenServer know that the call times out?

Thank you!

This is a feature introduced in OTP24:

A process alias is similar to a registered name that is used temporarily while a request is outstanding. If the request times out or the connection to the server is lost, the alias is deactivated which prevents a late reply from reaching the client.

2 Likes