gen_server: reply / 2: формат сообщения, отправленного клиенту - PullRequest
0 голосов
/ 24 июня 2019

Когда я звоню gen_server:reply/2:

gen_server:reply(From, Msg),

, клиент From получает сообщение в формате:

{Ref, Msg)

Я не могу найти документацию дляформат сообщения, отправленный gen_server:reply/2, и мне интересно, как я могу сопоставить шаблон с Ref в сообщении.В настоящее время я использую переменную "все равно" для Ref:

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

, что означает, что процесс, отличный от gen_server, потенциально может отправить моему клиенту сообщение, которое будет соответствовать {_Ref, Msg}пункт.

Ответы [ 2 ]

4 голосов
/ 25 июня 2019

В вызове gen_server:reply(From, Msg), From - это не просто клиент: на самом деле это кортеж, содержащий два значения: идентификатор процесса вызывающей стороны и уникальную ссылку.Мы можем видеть это в реализации gen_server:reply/2:

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

Идея состоит в том, что Tag является уникальным значением, предоставляемым вызывающим, так что вызывающий может различитьрезультат этого вызова из любого другого входящего сообщения:

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

В приведенном выше коде receive будет блокироваться, пока не получит ответ на этот самый вызов.

(gen_server:call/2выполняет действия, описанные выше, и дополнительно контролирует сервер в случае его сбоя и проверяет тайм-ауты.)

Причина, по которой это недокументировано, заключается в том, что он считается внутренней деталью реализации, подлежащей изменению, и пользователям рекомендуетсяполагаться на gen_server:call и gen_server:reply вместо генерации и сопоставления самих сообщений.


Большую часть времени вам вообще не нужно будет использовать gen_server:reply/2: процесс сервера получаетВызовите и обрабатывает его синхронно, возвращая reply кортеж:

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

Но иногда вы хотите, чтобы процесс сервера задерживал ответ на вызов, например, ожидание сетиork 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}.

В приведенном выше примере мы сохраняем значение From в состоянии сервера, а когда ответ приходит в виде сообщения Erlang, мы направляем его вызывающей стороне, которая блокируетпока не получит ответ.(Более реалистичный пример будет обрабатывать несколько запросов одновременно и каким-то образом сопоставлять входящие ответы с невыполненными запросами.)

1 голос
/ 24 июня 2019

Это gen.erl функция, используемая поведением gen_ *.Вы можете видеть вызов gen_event , вызов gen_server и вызов gen_statem .
Так как это работает?
Идея проста, когда вы звоните gen: call / 4 или gen:call(Process, Label, Request, Timeout), он контролирует Process.Так что erlang:monitor/2 дает ссылку.Он использует эту ссылку и отправляет сообщение Process в форме {Label, {self(), Ref}, Request}.После этого он ждет {Ref, Reply} для указанного Timeout и после получения ответа демонитирует Process.Также, если Process вылетает во время отправки Reply или даже если Process был мертвым pid до вызова, он получает {'DOWN', Ref, _, _, Reason}.

Например gen_server:call/2-3 Звоните gen:call(Prpcess, '$gen_call', Req, Timeout).Когда сервер Process (который является gen_server) получает его, он предполагает, что это запрос вызова, поэтому вызывает вашу функцию handle_call и т. Д.

...