Time out on Genserver.call during Genserver.cast

I’m a bit lost about the time outs on GenServer, as far as I understood, if you do a GenServer.call(Test, :test, 5000) and that :test function takes more than 5 seconds, the GenServer will time out. Something similar but not quite like that is happening to me. I have a GenServer call that only returns whatever there is in the state, but I have a quite expensive function that is called using a cast (it queries a few sites and updates the state), the problem is that if I call the cast, for example GenServer.cast(__MODULE__, :expensive_function) and before it finishes, I call the GenServer.call(Test, :return_status, 5000), it times out. Why is that happening? I would expect GenServer.call to return whatever is in the state at that point, not waiting until the cast is done before returning the state, does this make sense? or am I doing something wrong?

The expensive calculation in the cast is probably blocking the GenServer process and thus it can’t process any new messages until it has finished.

Lets say, you come to desk at 09:00 and find a note that you shall buy new printing paper from the store, then you leave the office go to the store and buy the paper, after you come back, there is a new note that you shall call a customer until 10:00, but you came back to office at 10:30 because the store is far away. Thats pretty much the same situation but a bad analogy :wink:

3 Likes

Perhaps this is what you mean but just to clarify:
It is not the GenServer that times out. It is the calling function that times out.

In reality you have two processes sending messages to each other. All messages on the BEAM are asynchronous, meaning that sending a message never fails, but you can also not know what happened to it (did it even reach its destination? Did the receiver crash? etc etc).

So a GenServer is an abstraction over these processes. The call is faking a synchronous call by sending a message to the GenServer and then waiting for it to reply. If no reply is received before timeout then it stops waiting and times out.

A cast is sending a message to the GenServer asynchronously and therefore doesn’t wait for a reply.

All processing in a process (and therefore GenServer) is serialized, meaning it can only handle one message at a time. When you tell the GenServer to do something expensive, it can’t handle any other messages. This is why your call times out. The process is busy handling your expensive cast and cannot return the state.

Generally you want to avoid doing anything expensive in a GenServer as it blocks. One way to work around this is doing the expensive operation in another process and just update the GenServer with the result, perhaps setting a status flag in the GenServer to avoid doing multiple expensive operations at a time. This would allow you to get the state from the GenServer while a new state is calculated. Hope this makes sense.

8 Likes

excellent! as always the quality of the answers is amazing. After reading both replies it makes sense, I’m just still struggling to think in terms of processes, but when I do think in processes, this makes sense, so thank you very much and I’ll take @cmkarlsson suggestion and make the expensive calculation in another process, thanks again!

3 Likes