новости сообщество форум вики
Erlang по-русски. Форум » Erlang »

Вопрос по gen_fsm

(17 posts)

  1. EvilBlueBeaver
    Участник

    Здравствуйте.
    Есть у меня конечный автомат, описывающий состояние некоторого устройства. У него есть такие состояния, как "свободен", "инициализирован", "подключен". На этапе иницализации загружается нужный драйвер с использованием NIF. Когда устройство инициализировано, требуется произвести его подключение. Вот здесь и кроется проблема, поскольку подключение длится достаточно долго, автомат зависает в этом состоянии и ничего не происходит. В итоге он отключается по таймауту. Аналогично происходит если я не использую NIF, а просто ставлю вместо нее задержку timer:sleep(5000). Вопрос, как можно сделать так, чтобы не зависало?

    Отправлено 1 год назад #
  2. Зависает процесс, который подключение инициировал? Если да, то вызывайте у fsm send_event - он асинхронный, или sync_send_event с указанием таймаута.

    Если беспокоит зависание самого fsm, т.е. состояние, когда к нему нельзя обратиться, т.к. он все еще подключение выполняет, то можно сделать так:
    - добавить еще одно состояние fsm - "подключение";
    - вынести работу с долгими Н?Фами в отдельный процесс. Этот новый процесс (например, пусть он будет gen_server) создается и получает асинхронную команду на подключение от fsm. А когда закончит подключаться, вызовет метод fsm на перевод его в состояние "подключен".

    Отправлено 1 год назад #
  3. EvilBlueBeaver
    Участник

    Асинхронный перевод состояний не очень подходит. А вот по поводу отдельного процесса надо подумать.
    Вдогонку такой вопрос, процесс, из которого вызывается переключение состояния автомата должен узнать, что состояние переключилось (то есть фактически произо?ло соединение). и каким-то образом надо ему об этом сообщить. У меня пока только такой вариант: Автомат запускает асинхронное переключение и передает функции адресата (pid процесса, который вызвал это переключение). gen_server который работает с NIF по завер?ении работы переводит автомат в состояние "подключено" и отсылает адресату сообщение об успехе перевода. В адресате после асинхронного переключения автомата просто стоять receive. Я правильно понял идею?

    Отправлено 1 год назад #
  4. Да, идею правильно, но в деталях я бы сделал по другому.

    Все процессы должны соответствовать OTP. Поэтому просто ждать в receive не правильно. Скорее, это будет специальный метод для вызова из Автомата.

    Pid инициирующего процесса удобнее хранить в Автомате, в его State. ? при переходе в состояние "Подключено" автомат сам может отправить сообщение (вызвать метод) инициирующему процессу.
    В этом случае процесс с Н?Фом не будет владеть ли?ней для него информацией о структуре системы. А это хоро?о, т.к. позволит в дальней?ем проще ее менять.

    Отправлено 1 год назад #
  5. EvilBlueBeaver
    Участник

    Кстати в продолжение темы.
    А чем может быть вызвано такое зависание gen_fsm? Это ?татное поведение такое или баг? Если ?татное, то где об этом можно прочесть?

    Отправлено 1 год назад #
  6. А зависания нет никакого. Просто функция долго работает и все (боль?е 5 секунд). До конца отработает, вернет результат. Либо смиритесь, либо перепи?ите NIF, чтобы он побыстрее отрабатывал.

    5 секунд - таймаут по умолчанию ожидания результата для синхронных вызовов во всем ОТР. Описано, например, здесь:
    http://www.erlang.org/doc/man/gen_fsm.html#sync_send_event-2

    А вообще, долго работающие функции - это нормально. Специально для них асинхронные вызовы предусмотрены. Долгую функцию всегда удобно в отдельном процессе запускать, чтобы остальная программа от нее не зависела. Да и убить отдельный процесс тоже всегда можно, если выполнение уж очень сильно затянулось.

    Отправлено 1 год назад #
  7. EvilBlueBeaver
    Участник

    Самое веселое, что в последнем ответе содержалась самая нужная инфа (я имею в виду про дефолтный таймаут в 5 секунд. Я просто в интерфейсном методе постаивл таймаут боль?е и все. Ну и согласно ва?ей рекомендации всю работу с Н?Ф вынес в отдельный процесс. Мне кажется это самый простой и логичный вариант.

    connect(Port, Device, Input) ->
    gen_fsm:sync_send_event(Port, {connect, Device, Input}, 30000).

    А вот Н?Ф ускорить никак не получится, дело в том, что там идет дозвон по gsm-модему и его время умень?ить никак нельзя.

    Отправлено 1 год назад #
  8. :) Наверное.
    В первом ответе тоже про таймаут писал.

    По превы?ению времени что делаете? Перезапускаете Автомат?

    Отправлено 1 год назад #
  9. EvilBlueBeaver
    Участник

    В общем я предпочел такую схему.
    Автомат контролирует корректные состояния, переходы и отваливания по таймауту. Отдельный gen_server занимается работой с NIF. ?з автоматы дергается gen_server. В функциях gen_server стоят таймауты. В случае таймаута я ловлю исключение и возвращаю автомату {error, Reason}. Автомат в таком случае не переключает свое состояние, а остается в прежнем и возвращает вызвав?ей его функции тот же самый {error, Reason}.

    Вроде все хоро?о, но есть одно "но", даже когда выбрасывается исключение на таймаут, gen_server все равно продолжает работу handle_call. Это я что-то не так делаю или так и должно быть? если второе, то как это победить?

    Отправлено 1 год назад #
  10. ?сключение происходит в процессе Автомата, т.к. это он ждет ответа и не дожидается его. ГенСервер с Н?Фом ничего не знает про то исключение, оно же не у него произо?ло. Процесс ГенСервера спокойно стоит на строке вызова Н?Ф и ждет возвращения управления. Как дождется, перейдет к выполнению следующей строки кода.

    Обработка исключения - это не гуд. Считается, что исключения в Эрланге надо отлавливать только в самом крайнем случае. А в остальных надо позволять процессу упасть.

    Более правильное поведение автомата: сразу вернуть управление и перейти в состояние "Подключение". Потом, когда подключение состоится, перейти в состояние "Подключен" и сообщить об этом верхнему процессу с помощью вызова какой-либо функции. Такой способ луч?е, т.к. в любой момент времени можно проверить его состояние. А вот если сделать ожидание в вызове call, то такой фокус уже не пройдет.

    А про то, как победить работу ГенСервера, когда результат уже не важен.. Сделать функцию stop(), которая вызовет gen_server:cast(?SERVER, stop), а в обработчике этого сообщения вернуть {stop, normal}.

    :) Короче, на пальцах тяжело объяснить. Немного позже выложу код, как я его вижу.

    Отправлено 1 год назад #
  11. Вот, что у меня получилось.
    Автомат:
    my_fsm.erl
    ------------------------------------

    -module(my_fsm).

    -behaviour(gen_fsm).

    %% API
    -export([start_link/0, connect/0, disconnect/0, stop/0, on_load/0, on_connect/0]).

    %% gen_fsm callbacks
    -export([init/1, idle/2, waiting/2, loaded/2, connected/2, handle_event/3,
    handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).

    -define(SERVER, ?MODULE).

    -record(state, {}).

    %%%===================================================================
    %%% API
    %%%===================================================================

    start_link() ->
    gen_fsm:start_link({local, ?SERVER}, ?MODULE, [], []).

    connect() ->
    gen_fsm:send_event(?SERVER, connect).

    disconnect() ->
    gen_fsm:send_all_state_event(?SERVER, disconnect).

    stop() ->
    gen_fsm:send_all_state_event(?SERVER, stop).

    on_load() ->
    gen_fsm:send_event(?SERVER, on_load).

    on_connect() ->
    gen_fsm:send_event(?SERVER, on_connect).

    %%%===================================================================
    %%% gen_fsm callbacks
    %%%===================================================================

    init([]) ->
    io:format("FSM state: IDLE~n"),
    {ok, idle, #state{}}.

    idle(connect, State) ->
    my_srv:start_link(),
    io:format("FSM state: WAITING~n"),
    {next_state, waiting, State};
    idle(_Event, State) ->
    io:format("FSM Ignore event: ~w~n", [_Event]),
    {next_state, idle, State}.

    waiting(on_load, State) ->
    my_srv:connect(),
    io:format("FSM state: LOADED~n"),
    {next_state, loaded, State};
    waiting(_Event, State) ->
    io:format("FSM Ignore event: ~w~n", [_Event]),
    {next_state, waiting, State}.

    loaded(on_connect, State) ->
    io:format("FSM state: CONNECTED~n"),
    % master_process:on_connect(),
    {next_state, connected, State};
    loaded(_Event, State) ->
    io:format("FSM Ignore event: ~w~n", [_Event]),
    {next_state, loaded, State}.

    connected(_Event, State) ->
    io:format("FSM Ignore event: ~w~n", [_Event]),
    {next_state, connected, State}.

    handle_event(disconnect, _StateName, State) ->
    my_srv:stop(),
    io:format("FSM state: IDLE~n"),
    {next_state, idle, State};

    handle_event(stop, _StateName, State) ->
    io:format("FSM Stop~n"),
    {stop, normal, State}.

    handle_sync_event(_Event, _From, StateName, State) ->
    {reply, ok, StateName, State}.

    handle_info(_Info, StateName, State) ->
    {next_state, StateName, State}.

    terminate(_Reason, _StateName, _State) ->
    ok.

    code_change(_OldVsn, StateName, State, _Extra) ->
    {ok, StateName, State}.

    Отправлено 1 год назад #
  12. Сервер, эмулирующий работу с NIF:
    my_srv.erl
    ------------------------------------------

    -module(my_srv).

    -behaviour(gen_server).

    -export([start_link/0, connect/0, stop/0]).
    -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
    terminate/2, code_change/3]).

    -define(SERVER, ?MODULE).

    -record(state, {}).

    %%% API

    start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

    load() ->
    gen_server:cast(?SERVER, load).

    connect() ->
    gen_server:cast(?SERVER, connect).

    stop() ->
    gen_server:cast(?SERVER, stop).

    %%% gen_server callbacks

    init([]) ->
    io:format("SRV INIT~n"),
    load(),
    {ok, #state{}}.

    handle_call(_Request, _From, State) ->
    {reply, ok, State}.

    handle_cast(load, State) ->
    io:format("SRV Message: load~n"),
    long_time_nif(),
    my_fsm:on_load(),
    {noreply, State};

    handle_cast(connect, State) ->
    io:format("SRV Message: connect~n"),
    long_time_nif(),
    my_fsm:on_connect(),
    {noreply, State};

    handle_cast(stop, State) ->
    io:format("SRV Stop~n"),
    {stop, normal, State}.

    handle_info(_Info, State) ->
    {noreply, State}.

    terminate(_Reason, _State) ->
    ok.

    code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

    long_time_nif() ->
    timer:sleep(5000).

    Отправлено 1 год назад #
  13. ? предполагается, что есть еще некоторый модуль и процесс master_process, который вызывает my_fsm:connect(), а также который оповещается об удачном подключении с помощью функции master_process:on_connect().

    Порядок работы:
    1. my_fsm:start_link().
    2. my_fsm:connect().
    3. my_fsm:disconnect().

    connect и disconnect можно вызывать в произвольной последовательности, как ожидая окончания переключения, так и не ожидая. Автомат все равно правильно отследит состояние Сервера.

    Фунции on_load и on_connect - это типа события Сервера, о которых оповещается Автомат.

    my_srv:long_time_nif() - эмуляция долго работающей NIF.

    Отправлено 1 год назад #
  14. Еще предполагается, что Автомат и Сервер присутствуют в единственном экземпляре. Поэтому никакие П?Ды нигде не передаются, а общение идет через зарегистрированные имена.

    Но навернуть сюда еще множественную реализацию не сложно. Если надо, могу дополнить.

    Отправлено 1 год назад #
  15. EvilBlueBeaver
    Участник

    Спасибо за помощь, попробую разобраться. Все же мне кажется странной такая сильная связность автомата и сервера. С множественностью проблем не возникнет у меня уж точно.

    Как я понимаю, с таймаутами здесь проблем быть не может, поскольку в случае тайм-аута Н?Ф, просто не будет вызван метод автомата. А сделать так, чтобы автомат возвращался в исходное состояние также не проблема.

    Отправлено 1 год назад #
  16. EvilBlueBeaver
    Участник

    Еще вопрос. Можно ли как-то остановить выполнение метода gen_server'а не перезапуская сам gen_server?

    Отправлено 1 год назад #
  17. Остановить выполнение метода можно убив процесс.
    Например, так:

    exit(Pid, Reason).

    Pid - убиваемый процесс (Сервер).
    Только тут надо учесть состояние флага trap_exit у убиваемого процесса и наличие линков с другими процессами. Если они есть, то будут довольно сложные танцы получаться.

    Отправлено 1 год назад #

RSS экспорт этой темы

Отправить сообщение

Вы должны войти в систему, чтобы оставлять сообщения.

 
 

так же

Популярные тэги



Currently online

No Members around.

сообщество

http://groups.google.com/group/erlang-russian/feed/rss_v2_0_msgs.xml