Using send_request, check_response with multiple processes

If I’ve got two processes, A and B, and I use send_request/4 with both, like this:

ReqIds2 = gen_statem:send_request(A, ReqA, LabelA, ReqIds),
ReqIds3 = gen_server:send_request(B, ReqB, LabelB, ReqIds2),
%...

Then I get sent messages that look like {[alias|#Ref<...>],RespA}, and there’s no obvious way to figure out which of the following is correct:

MaybeA = gen_statem:check_response(Msg, ReqIds, true),
MaybeB = gen_server:check_response(Msg, ReqIds, true),

A few questions

  • Are the request_id_collection types compatible between gen_server and gen_statem? The fact that you construct them with gen_server:reqids_new/0 and gen_statem:reqids_new/0 suggests not, even if they are. Perhaps these helpers should have been in gen? Although I don’t think that’s a public module…?
  • Should I be using the same ReqIds for multiple processes? Is this sensible?
  • How do I differentiate between A and B responses? Labels (due to the API) feel like they belong to gen_server and gen_statem, rather than to the caller or A and B

Possible enhancement

The other problem with response messages is that they’re not tagged, which makes them hard to distinguish from normal handle_info or handle_event(info, ..., which is kinda annoying.

Is it worth adding gen_statem:send_request/5 which takes a Tag parameter so that the response message is {Tag, Response}?

Both gen_server:reqids_new/0 and gen_statem:reqids_new/0 call gen:reqids_new/0. Respectively the same for send_request/2, receive_response/2 and check_response/2, just with some error fiddling around them.

1 Like

Why send the tag back and forth. Couldn‘t you locally tag the references you retain while waiting for the response?

The response is sent as-is, which means that handle_event needs to look like this:

handle_event(info, Info, State, StateData = #state{req_ids = ReqIds}) ->
    % Can't tell the difference between send_request responses and normal info messages,
   %  so we have to check them first.
    check_response(
        gen_statem:check_response(Info, ReqIds, true),
        Info, State, StateData).

check_response({{reply, Reply}, Label, NewReqIds},
               Info, State, StateData) -> ...;
check_response({{error, {Reason, ServerRef}}, Label, NewReqIds},
               Info, State, StateData) -> ...;
check_response(Result, Info, State, StateData) when
    Result == no_request; Result == no_reply
->
    handle_info(Info, State, StateData).

The response is opaque, so we can’t match on it directly.

Contrast this with monitor/2:

handle_event({'DOWN', Ref, process, Pid, Reason}, ...) -> ...;

…where the 'DOWN' tag makes it easy to recognise. And, in fact, monitor/3 allows you to replace the tag with something custom (this is what I was hoping any enhancement would look like).

The Label isn’t helpful for this, since it’s stored in the ReqIdCollection, so it’s only available after you’ve called check_response.

Aside, for anyone wondering: I can’t (easily) use the labels for this, because I’ve already overloaded them with some other information I wanted to keep on the caller’s side. See kafine/src/connection/kafine_connection.erl at 0.12.1 · happening-oss/kafine · GitHub

This was done intentionally, so there is no need for the server side to know if it was a call
or async_call.

To be able to tag the response we would have needed a handle_async_call callback as well,
it would look exactly like the corresponding handle call, but would add the tag on the reply.

I had assumed (based on some poking around) that it would be possible to hide the tags inside the gen:call machinery, so that gen_server et. al. don’t need to see it. Did I miss a detail somewhere?