Genserver.call with timeout doesn't end up with :timeout message

Now the problem is that your calling process will still receive a message with the response, because the GenServer will send it, as it will have received the call.

So what you can do is wrap your call in a task:

def calculate_checksum() do
    Task.async(fn ->
      try do
        GenServer.call(__MODULE__, :calculate, 15)
      catch
        :exit, {:timeout, {GenServer, :call, _}} -> {:error, :timeout}
      end
    end)
    |> Task.await()
  end

Note that Task.await also has a timeout, and like GenServer.call/3 the default is 5000, You don’t care as you are targetting 15ms here. But you can set it to :infinity it that makes sense.

The task process is linked to the caller, so if you have an error/exit that is not the timeout, it will still propagate to your calling process, no risk of missing errors.

The task will return with the checksum or {:error, :timeout}. If there is a timeout, as I have said the GenServer will still send the response. But it will send it to the task process, which will by dead by then. And sending a message to a non-existent process is fine in Elixir. That is the way to safely ignore the response.

Finally, as you handle a short timeout and ignore the response, keep in mind that the GenServer will still have to calculate the checksum, it will still do the requested work to send the response which will be ignored.