GenServer docs: "handle_cast ... should be used sparingly" Why?

Interesting approach.

def handle_call(:foo, from, state) do
  GenServer.reply(from, :ok) # !!! I almost missed this part !!!

  new_state = do_work()

  {:noreply, new_state}
end

To me this further illustrates that one needs to be very clear on what the reply really “means” and what the reply is meant to accomplish.

  • In the simplest and most common case one is simply waiting for a result that one needs to continue - though that opens up other questions that could be worth consideration:
    A) Should I be waiting for a result or should I be re-organizing my logic to work in a GenStage kind-of-way?
    B) Do I really want to be blocked for the result?
  • Waiting for an :ok reply can mean
    a) I just want to make sure you got the message - past that if something goes wrong it’s entirely your (and your supervisors) problem.
    b) I really need to know that my request was processed to completion - if it didn’t, I need to “let my supervisor know” (i.e. I need to fail).

Building Non Blocking Erlang Apps is a nice tactic for a server to remain responsive even when dealing with slow services/resources. But what if a client of such a server doesn’t want to be blocked either?

This is where I thought the “call-with-acknowledge/cast-result-back-to-client” interaction pattern might come in handy. But that would mean having to rework the server’s interface. It probably would be simpler and better for the client to spin-off a proxy process (Task.start[_link]?) for the sole purpose of handling one request with that server - it doesn’t matter if the proxy process is blocked - because that’s it’s job. The client process can go off and do it’s thing, until the proxy reports back with a result or error (and terminates).

A one-off proxy process may also be better when dealing with timeouts because stale messages wouldn’t have a mailbox to go to, so the “real” client isn’t burdened with clearing out stale messages.

The point being process interaction isn’t simply limited to cast and call but can be customized with process links, monitors and other (short lived) processes; so it would be a big mistake to mentally equate a call to a process to a function invocation when in fact process interactions can be handled in many and more nuanced ways.

Yes - I know it looks like I’m overthinking (over-engineering) this but

In preparing for battle I have always found that plans are useless, but planning is indispensable. Dwight D. Eisenhower

i.e. it’s important to know the full spectrum of your available options - or in my case before choosing the simplest possible approach I want to assess all available options first so that I can legitimately select the simplest option as the appropriate and best option - rather than just selecting something “by default”.

7 Likes