Search code examples
erlangerlang-otpgen-server

gen_server:reply/2: format of message sent to client


When I call gen_server:reply/2:

gen_server:reply(From, Msg),

the client, From, receives a message with the format:

{Ref, Msg)

I can't find any documentation for the message format sent by gen_server:reply/2, and I'm wondering how I can pattern match the Ref in the message. Currently, I use a don't care variable for the Ref:

receive
    {_Ref, Msg} -> Msg;
    Other -> Other
end

which means that a process other than the gen_server could potentially send my client a message that would match the {_Ref, Msg} clause.


Solution

  • In the call gen_server:reply(From, Msg), From is not simply the client: it is in fact a tuple containing two values, the process id of the caller and a unique reference. We can see this in the implementation of gen_server:reply/2:

    %% -----------------------------------------------------------------
    %% Send a reply to the client.
    %% -----------------------------------------------------------------
    reply({To, Tag}, Reply) ->
        catch To ! {Tag, Reply}.
    

    The idea is that Tag is a unique value provided by the caller, so that the caller can distinguish the result from this call from any other incoming message:

    Ref = make_ref(),
    MyServer ! {'$gen_call', {self(), Ref}, foo},
    receive
        {Ref, Reply} -> io:format("Result of foo call: ~p~n", [Reply])
    end
    

    In the code above, the receive will block until it gets a response to this very call.

    (gen_server:call/2 does something like the above, and additionally monitors the server in case it crashes, and checks for timeouts.)

    The reason this is undocumented is that it is considered an internal implementation detail subject to change, and users are advised to rely on gen_server:call and gen_server:reply instead of generating and matching the messages themselves.


    Most of the time you wouldn't need to use gen_server:reply/2 at all: the server process receives a call and handles it synchronously, returning a reply tuple:

    handle_call(foo, _From, State) ->
        %% ignoring 'From' here, because we're replying immediately
        {reply, foo_result, State}.
    

    But sometimes you'd want the server process to delay replying to the call, for example waiting for network input:

    handle_call(foo, From, State) ->
        send_request(foo),
        NewState = State#state{pending_request = From},
        {noreply, NewState}.
    
    handle_info({received_response, Response}, State = #state{pending_request = From}) ->
        gen_server:reply(From, Response),
        NewState = State#state{pending_request = undefined},
        {noreply, NewState}.
    

    In the example above, we save the From value in the server state, and when the response comes in as an Erlang message, we forward it to the caller, which will block until it gets the response. (A more realistic example would handle multiple requests concurrently and match incoming responses to outstanding requests somehow.)