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