Message queue length of a GenServer

If you proceed down this path and wish to return an {:error, :queue_full} you probably don’t want to do it from the handle_call. I’m assuming that if there are lots of messages that you want to discard the newest messages: the ones at the end of the queue. By putting the condition in handle_call if will affect the response to the message at the head of the queue.

In sandwich shop terms (because I’m hungry right now), putting the check and error response in handle_call is equivalent to getting in line at a sandwich shop before the lunch rush, waiting a little bit, lunch rush hits, getting to the counter to order and being told, “I’m sorry. I can’t make you a sandwich; the line is too long.” When I think what you want is a bouncer at the door that says, “I’m sorry. The line can’t extend outside.”

If you must implement this yourself rather than using some of the suggestions from other folks, you may wish to put the check in the client function rather than handle_call.

Here’s a quick proof of concept

defmodule OverloadedGenServer do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, [])
  end

  def init(_) do
    {:ok, 0}
  end

  def add_num(pid, num) do
    case Process.info(pid, :message_queue_len) do
      {_, len} when len > 5 ->
        {:error, :queue_full}
      _ ->
        GenServer.call(pid, {:add_num, num})
    end
  end

  def handle_call({:add_num, num}, _from, prior_num) do
    new_num = prior_num + num
    Process.sleep(500)
    {:reply, new_num, new_num}
  end
end

{:ok, pid} = OverloadedGenServer.start_link()

for n <- 1..10 do
  Task.async(fn -> OverloadedGenServer.add_num(pid, n) end)
end
|> Enum.map(&Task.await/1)
|> IO.inspect

Process.exit(pid, :kill)

with output

$ elixir queue_test.exs
[
  1,
  3,
  6,
  10,
  15,
  21,
  {:error, :queue_full},
  {:error, :queue_full},
  {:error, :queue_full},
  {:error, :queue_full}
]
** (EXIT from #PID<0.73.0>) killed