I know it's pretty much impossible to have a GenServer process call itself because you essentially hit a deadlock. But, I'm curious if there's a preferred way to do this kind of thing.
Assume the following scenario: I've got a queue that I'm popping things from. If the queue is ever empty, I want to refill it. I could structure it like so:
def handle_call(:refill_queue, state) do
new_state = put_some_stuff_in_queue(state)
{:reply, new_state}
end
def handle_call(:pop, state) do
if is_empty_queue(state) do
GenServer.call(self, :refill_queue)
end
val,new_state = pop_something(state)
{:reply, val, new_state}
end
The big problem here is that this will deadlock when we try to refill the queue. One solution that I've used in the past is to use cast
more so it doesn't deadlock. Like so (change call
to cast
for refill)
def handle_cast(:refill_queue, state) do
But in this case, I think it won't work, since the async cast to refill the queue might return in the pop
case before actually filling the queue meaning I'll try to pop off an empty queue.
Anyways, the core question is: What is the best way to handle this? I assume the answer is to just call put_some_stuff_in_queue
directly inside the pop
call, but I wanted to check. In other words, it seems like the right thing to do is make handle_call
and handle_cast
as simple as possible and basically just wrappers to other functions where the real work happens. Then, create as many handle_*
functions as you need to cover all the possible cases you'll deal with, rather than having handle_call(:foo)
in turn call handle_call(:bar)
.
refill_queue
a plain function and call it fromhandle_call(:pop)
if you need it to be synchronous. Otherwise you have several options for handling it async (send another message toself
, have another process handling refilling, etc) – TinworksGenStage
, sounds like it provides the functionality you are looking for. – Kurtz