Здравствуйте.
Есть у меня конечный автомат, описывающий состояние некоторого устройства. У него есть такие состояния, как "свободен", "инициализирован", "подключен". На этапе иницализации загружается нужный драйвер с использованием NIF. Когда устройство инициализировано, требуется произвести его подключение. Вот здесь и кроется проблема, поскольку подключение длится достаточно долго, автомат зависает в этом состоянии и ничего не происходит. В итоге он отключается по таймауту. Аналогично происходит если я не использую NIF, а просто ставлю вместо нее задержку timer:sleep(5000). Вопрос, как можно сделать так, чтобы не зависало?
-
Отправлено 1 год назад #
-
Зависает процесс, который подключение инициировал? Если да, то вызывайте у fsm send_event - он асинхронный, или sync_send_event с указанием таймаута.
Если беспокоит зависание самого fsm, т.е. состояние, когда к нему нельзя обратиться, т.к. он все еще подключение выполняет, то можно сделать так:
- добавить еще одно состояние fsm - "подключение";
- вынести работу с долгими Н?Фами в отдельный процесс. Этот новый процесс (например, пусть он будет gen_server) создается и получает асинхронную команду на подключение от fsm. А когда закончит подключаться, вызовет метод fsm на перевод его в состояние "подключен".Отправлено 1 год назад # -
Асинхронный перевод состояний не очень подходит. А вот по поводу отдельного процесса надо подумать.
Вдогонку такой вопрос, процесс, из которого вызывается переключение состояния автомата должен узнать, что состояние переключилось (то есть фактически произо?ло соединение). и каким-то образом надо ему об этом сообщить. У меня пока только такой вариант: Автомат запускает асинхронное переключение и передает функции адресата (pid процесса, который вызвал это переключение). gen_server который работает с NIF по завер?ении работы переводит автомат в состояние "подключено" и отсылает адресату сообщение об успехе перевода. В адресате после асинхронного переключения автомата просто стоять receive. Я правильно понял идею?Отправлено 1 год назад # -
Да, идею правильно, но в деталях я бы сделал по другому.
Все процессы должны соответствовать OTP. Поэтому просто ждать в receive не правильно. Скорее, это будет специальный метод для вызова из Автомата.
Pid инициирующего процесса удобнее хранить в Автомате, в его State. ? при переходе в состояние "Подключено" автомат сам может отправить сообщение (вызвать метод) инициирующему процессу.
В этом случае процесс с Н?Фом не будет владеть ли?ней для него информацией о структуре системы. А это хоро?о, т.к. позволит в дальней?ем проще ее менять.Отправлено 1 год назад # -
Кстати в продолжение темы.
А чем может быть вызвано такое зависание gen_fsm? Это ?татное поведение такое или баг? Если ?татное, то где об этом можно прочесть?Отправлено 1 год назад # -
А зависания нет никакого. Просто функция долго работает и все (боль?е 5 секунд). До конца отработает, вернет результат. Либо смиритесь, либо перепи?ите NIF, чтобы он побыстрее отрабатывал.
5 секунд - таймаут по умолчанию ожидания результата для синхронных вызовов во всем ОТР. Описано, например, здесь:
http://www.erlang.org/doc/man/gen_fsm.html#sync_send_event-2А вообще, долго работающие функции - это нормально. Специально для них асинхронные вызовы предусмотрены. Долгую функцию всегда удобно в отдельном процессе запускать, чтобы остальная программа от нее не зависела. Да и убить отдельный процесс тоже всегда можно, если выполнение уж очень сильно затянулось.
Отправлено 1 год назад # -
Самое веселое, что в последнем ответе содержалась самая нужная инфа (я имею в виду про дефолтный таймаут в 5 секунд. Я просто в интерфейсном методе постаивл таймаут боль?е и все. Ну и согласно ва?ей рекомендации всю работу с Н?Ф вынес в отдельный процесс. Мне кажется это самый простой и логичный вариант.
connect(Port, Device, Input) ->
gen_fsm:sync_send_event(Port, {connect, Device, Input}, 30000).
А вот Н?Ф ускорить никак не получится, дело в том, что там идет дозвон по gsm-модему и его время умень?ить никак нельзя.Отправлено 1 год назад # -
:) Наверное.
В первом ответе тоже про таймаут писал.По превы?ению времени что делаете? Перезапускаете Автомат?
Отправлено 1 год назад # -
В общем я предпочел такую схему.
Автомат контролирует корректные состояния, переходы и отваливания по таймауту. Отдельный gen_server занимается работой с NIF. ?з автоматы дергается gen_server. В функциях gen_server стоят таймауты. В случае таймаута я ловлю исключение и возвращаю автомату {error, Reason}. Автомат в таком случае не переключает свое состояние, а остается в прежнем и возвращает вызвав?ей его функции тот же самый {error, Reason}.Вроде все хоро?о, но есть одно "но", даже когда выбрасывается исключение на таймаут, gen_server все равно продолжает работу handle_call. Это я что-то не так делаю или так и должно быть? если второе, то как это победить?
Отправлено 1 год назад # -
?сключение происходит в процессе Автомата, т.к. это он ждет ответа и не дожидается его. ГенСервер с Н?Фом ничего не знает про то исключение, оно же не у него произо?ло. Процесс ГенСервера спокойно стоит на строке вызова Н?Ф и ждет возвращения управления. Как дождется, перейдет к выполнению следующей строки кода.
Обработка исключения - это не гуд. Считается, что исключения в Эрланге надо отлавливать только в самом крайнем случае. А в остальных надо позволять процессу упасть.
Более правильное поведение автомата: сразу вернуть управление и перейти в состояние "Подключение". Потом, когда подключение состоится, перейти в состояние "Подключен" и сообщить об этом верхнему процессу с помощью вызова какой-либо функции. Такой способ луч?е, т.к. в любой момент времени можно проверить его состояние. А вот если сделать ожидание в вызове call, то такой фокус уже не пройдет.
А про то, как победить работу ГенСервера, когда результат уже не важен.. Сделать функцию stop(), которая вызовет gen_server:cast(?SERVER, stop), а в обработчике этого сообщения вернуть {stop, normal}.
:) Короче, на пальцах тяжело объяснить. Немного позже выложу код, как я его вижу.
Отправлено 1 год назад # -
Вот, что у меня получилось.
Автомат:
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 год назад # -
Сервер, эмулирующий работу с 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 год назад # -
? предполагается, что есть еще некоторый модуль и процесс 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 год назад # -
Еще предполагается, что Автомат и Сервер присутствуют в единственном экземпляре. Поэтому никакие П?Ды нигде не передаются, а общение идет через зарегистрированные имена.
Но навернуть сюда еще множественную реализацию не сложно. Если надо, могу дополнить.
Отправлено 1 год назад # -
Спасибо за помощь, попробую разобраться. Все же мне кажется странной такая сильная связность автомата и сервера. С множественностью проблем не возникнет у меня уж точно.
Как я понимаю, с таймаутами здесь проблем быть не может, поскольку в случае тайм-аута Н?Ф, просто не будет вызван метод автомата. А сделать так, чтобы автомат возвращался в исходное состояние также не проблема.
Отправлено 1 год назад # -
Еще вопрос. Можно ли как-то остановить выполнение метода gen_server'а не перезапуская сам gen_server?
Отправлено 1 год назад # -
Остановить выполнение метода можно убив процесс.
Например, так:exit(Pid, Reason).
Pid - убиваемый процесс (Сервер).
Только тут надо учесть состояние флага trap_exit у убиваемого процесса и наличие линков с другими процессами. Если они есть, то будут довольно сложные танцы получаться.Отправлено 1 год назад #
