Question

I'm looking for good examples of Erlang asynchronous message handling with gen_server:cast/2.

I've seen an example in the OTP ssh module, which receives a request through Module:handle_cast/2, holding it in a local queue of the module, and sending back a reply message corresponding to the request later, by explicitly sending a message to the caller. When I tried to read it, I could barely keep track of the code, and wasn't able to grasp the idea.

A piece of pseudo code is appreciated.

Was it helpful?

Solution

I believe you're referring to the ssh_connection_manager module.

When you perform a gen_server:cast/2, the request is handled in the Module:handle_cast/2 function. A couple of things to notice here:

  • In the handle_cast parameters you don't have information about the sender, so you cannot - unless you send this information within the message itself - send back some result to it.
  • The client, after issuing a gen_server:cast/2, won't wait for a reply. Actually, it doesn't even care if the message arrived or not (with some exceptions).
  • In an handle_cast/2, you can just return a noreply or a stop tuple, so no way to return a reply there.

Said that, the idea behind the code you've been looking at should be (simplifying things):

  • An initial synchronous gen_server:call/2 is made
  • The client's From is passed to the server and saved into its state (in reality, an extra process seems to be created with that argument). This trick is useful when you are not able to compute the return value at the time of the handle_call.
  • At this point you have to two possibilities, depending if you need more information to compute your result from other clients (A) or from the same client (B):

    • A. Return something like {noreply NewState} in the handle_call. This will give you the possibility to handle other requests while the client is still holding. When the result is ready you can send it back to the client using a gen_server:reply().
    • B. Return {reply, ok, State} to the client. The client will continue execution, maybe performing a series of cast/2. Then, the client will ask you the final result with a new gen_server:call/2.

OTHER TIPS

Normally you don't expect a direct reply when sending a cast, otherwise you would use gen_server:call.

A real world example, I have a gen_server that handles some "channels" and there is lots of use to append the channel name to error logging. The channel name is stored in the state of the gen_server. In order not to hold up the process who wants to log an error I don't use a "get name" synchronous call to get the name an prepend but send the messages with casts:

error(Pid, Tags) ->
    gen_server:cast(Pid, {log, error_report, Tags}).

warning(Pid, Tags) ->
    gen_server:cast(Pid, {log, warning_report, Tags}).

info(Pid, Tags) ->
    gen_server:cast(Pid, {log, info_report, Tags}).

The cast is handled in a quite simple handler, that does not return.

handle_cast({log, Report, Tags}, #state{name=Name}=State) ->
    error_logger:Report([{chan, Name} | Tags]),
    {noreply, State};

If you have asynchronous messages to send back, this is totally independent of the handling of the cast. You somehow need to know where to send these messages which you have to store in the State somehow, or you are using a fixed name.

Normally you shouldn't just send a message but rather call a function of the receiving process module (which just might be another cast or plain message sending).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top