gen_statem
gen_statem
模块
gen_statem
模块摘要
泛型状态机行为。
描述
此行为模块提供了一个状态机。两个callback modes
支持:
- 一个用于有限状态机(例如
gen_fsm
),它要求状态是一个原子并将该状态用作当前回调函数的名称
- 一个对所有状态使用一个回调函数的状态数据类型没有限制
注
这是Erlang/OTP 19.0中的一个新行为。它已经过彻底的审查,足够稳定,至少可以供两个大型OTP应用程序使用,并且将继续使用。根据用户的反馈,我们不期望,但可以发现有必要对Erlang/OTP 20.0进行一些不向后兼容的小更改。
该gen_statem
行为取代gen_fsm
在Erlang/ OTP 20.0。它具有相同的功能并添加了一些非常有用的功能:
- 收集了状态代码。
- 任意项状态
- 事件延迟。
- 自生事件。
- 状态超时时间。
- 多个通用名称超时。
- 绝对超时时间。
- 自动状态输入呼叫。
- 来自请求以外的其他状态的答复。
- 倍数
sys
可追踪的答复。
gen_statem的回调模型与gen_fsm的回调模型不同,但从gen_fsm重写为gen_statem仍然相当容易。
gen_statem
使用此模块实现的通用状态机进程()具有一组标准接口函数,并包含用于跟踪和错误报告的功能。它也适合于OTP监督树。有关更多信息,请参阅OTP Design Principles
。
gen_statem
假定所有特定部件位于导出预定义函数集的回调模块中。行为函数和回调函数之间的关系如下所示:
gen_statem module Callback module
----------------- ---------------
gen_statem:start
gen_statem:start_link -----> Module:init/1
Server start or code change
-----> Module:callback_mode/0
gen_statem:stop -----> Module:terminate/3
gen_statem:call
gen_statem:cast
erlang:send
erlang:'!' -----> Module:StateName/3
Module:handle_event/4
- -----> Module:terminate/3
- -----> Module:code_change/4
事件是不同的types
,所以回调函数可以知道事件的起源以及如何响应。
如果回调函数失败或返回错误值,则gen_statem
终止,除非另有说明。但是,类的异常throw
不被视为错误,而是作为所有回调函数的有效返回。
“状态回调
“对于一个特定的state
在...gen_statem
对此状态下的所有事件调用的回调函数。它是根据以下几个因素选择的callback mode
回调模块用回调函数定义的Module:callback_mode/0
...
当回调模式为state_functions时,该状态必须是原子并用作状态回调名称; 请参阅Module:StateName / 3。 由于gen_statem引擎根据状态名称进行分支,因此会将一个特定状态的所有代码收集到一个函数中。 请注意,在此模式下,回调函数Module:terminate / 3会使状态名称终止不可用。
当callback mode
是handle_event_function
时,状态可以是任何术语和状态回调名称Module:handle_event/4
。根据您的需要,这可以很容易地根据状态或事件进行分支。要小心在哪些状态下处理哪些事件,以便您不会意外推迟事件,从而永远创建无限的繁忙循环。
在gen_statem
到达顺序排队,进入的事件,并提出这些对state callback
的顺序。状态回调可以推迟一个事件,以便在当前状态下不重试。状态更改后,队列将重新启动并显示推迟的事件。
该gen_statem
事件队列模型是足够选择性接收以模拟正常过程消息队列。推迟事件对应于在接收语句中不匹配它,并且改变状态对应于输入新的接收语句。
该state callback
可插入使用的事件action()
next_event
和这样的事件被插入作为下呈现给状态回调。就好像它是最古老的传入事件一样。专用event_type()
internal
可以用于这样的事件,使他们不可能错误的外部事件。
插入一个事件代替了调用你自己的状态处理函数的技巧,你经常不得不采用这种方法,例如,gen_fsm
强制在其他人之前处理插入的事件。
无论何时进入新状态,gen_statem
引擎都可以自动进行专门的呼叫state callback
; 见state_enter()
。这是为了编写所有状态条目通用的代码。另一种方法是在状态转换时插入事件,但必须在需要的任何地方执行。
注
gen_statem
例如,如果你在一个状态中推迟一个事件,然后调用另一个状态的回调,那么你没有改变状态,因此推迟的事件不会被重试,这是合乎逻辑的,但可能会引起混淆。
有关状态转换的详细信息,请参见type transition_option()
。
gen_statem
处理系统消息,如下所述sys
。该sys
模块可用于调试a gen_statem
。
请注意,gen_statem
不会自动捕获出口信号,这必须在回调模块中明确启动(通过调用process_flag(trap_exit, true)
。
除非另有说明,否则如果指定的内容gen_statem
不存在或者指定了错误的参数,则此模块中的所有功能都会失败。
这个gen_statem
过程可能会进入冬眠; 见proc_lib:hibernate/3
。当a state callback
或者在返回列表中Module:init/1
指定时完成。此功能可用于回收进程堆内存,而服务器预计会长时间处于空闲状态。但是,请谨慎使用此功能,因为在每个事件之后使用休眠方式的成本太高; 见。hibernateActionserlang:hibernate/3
例
以下示例显示了用于实现切换按钮的简单按钮模型。您可以按下按钮,它会回复它是否打开,并且您可以要求计算它已被按下来打开的次数。callback mode
state_functions
以下是完整的回调模块文件pushbutton.erl
:
-module(pushbutton).
-behaviour(gen_statem).
-export([start/0,push/0,get_count/0,stop/0]).
-export([terminate/3,code_change/4,init/1,callback_mode/0]).
-export([on/3,off/3]).
name() -> pushbutton_statem. % The registered server name
%% API. This example uses a registered name name()
%% and does not link to the caller.
start() ->
gen_statem:start{local,name()}, ?MODULE, [], []).
push() ->
gen_statem:call(name(), push).
get_count() ->
gen_statem:call(name(), get_count).
stop() ->
gen_statem:stop(name()).
%% Mandatory callback functions
terminate(_Reason, _State, _Data) ->
void.
code_change(_Vsn, State, Data, _Extra) ->
{ok,State,Data}.
init([]) ->
%% Set the initial state + data. Data is used only as a counter.
State = off, Data = 0,
{ok,State,Data}.
callback_mode() -> state_functions.
%%% state callback(s)
off{call,From}, push, Data) ->
%% Go to 'on', increment count and reply
%% that the resulting status is 'on'
{next_state,on,Data+1,[{reply,From,on}]};
off(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, Data).
on{call,From}, push, Data) ->
%% Go to 'off' and reply that the resulting status is 'off'
{next_state,off,Data,[{reply,From,off}]};
on(EventType, EventContent, Data) ->
handle_event(EventType, EventContent, Data).
%% Handle events common to all states
handle_event{call,From}, get_count, Data) ->
%% Reply with the current count
{keep_state,Data,[{reply,From,Data}]};
handle_event(_, _, Data) ->
%% Ignore all other events
{keep_state,Data}.
以下是运行该会话时的shell会话:
1> pushbutton:start().
{ok,<0.36.0>}
2> pushbutton:get_count().
0
3> pushbutton:push().
on
4> pushbutton:get_count().
1
5> pushbutton:push().
off
6> pushbutton:get_count().
1
7> pushbutton:stop().
ok
8> pushbutton:push().
** exception exit: {noproc,{gen_statem,call,[pushbutton_statem,push,infinity]}}
in function gen:do_for_proc/2 (gen.erl, line 261)
in call from gen_statem:call/3 (gen_statem.erl, line 386)
为了比较样式,下面使用相同的示例,或者在上面的示例文件的函数之后替换代码:callback mode
handle_event_functioninit/1pushbutton.erl
callback_mode() -> handle_event_function.
%%% state callback(s)
handle_event{call,From}, push, off, Data) ->
%% Go to 'on', increment count and reply
%% that the resulting status is 'on'
{next_state,on,Data+1,[{reply,From,on}]};
handle_event{call,From}, push, on, Data) ->
%% Go to 'off' and reply that the resulting status is 'off'
{next_state,off,Data,[{reply,From,off}]};
%%
%% Event handling common to all states
handle_event{call,From}, get_count, State, Data) ->
%% Reply with the current count
{next_state,State,Data,[{reply,From,Data}]};
handle_event(_, _, State, Data) ->
%% Ignore all other events
{next_state,State,Data}.
数据类型
server_name() =
{global, GlobalName :: term()} |
{via, RegMod :: module(), Name :: term()} |
{local, atom()}
启动时要使用的名称规范。gen_statem
服务器。见start_link/3
和server_ref()
下面。
server_ref() =
pid() |
(LocalName :: atom()) |
{Name :: atom(), Node :: atom()} |
{global, GlobalName :: term()} |
{via, RegMod :: module(), ViaName :: term()}
当寻址时使用的服务器规范gen_statem
服务器。见call/2
和server_name()
上面。
可以是:
pid() | LocalName
在gen_statem
本地注册。
{Name,Node}
这gen_statem
是在另一个节点上本地注册的。
{global,GlobalName}
在gen_statem
全球注册global
。
{via,RegMod,ViaName}
gen_statem在另一个进程注册表中注册。 注册表回调模块RegMod将导出函数register_name / 2,unregister_name / 1,whereis_name / 1和send / 2,这些函数的行为与全局中的相应函数类似。 因此,{via,global,GlobalName}与{global,GlobalName}相同。
debug_opt() =
{debug,
Dbgs ::
[trace | log | statistics | debug | {logfile, string()}]}
gen_statem
通过,启动服务器时可以使用的调试选项enter_loop/4-6
。
对于每个输入项Dbgs
,sys
都调用相应的函数。
hibernate_after_opt() =
{hibernate_after, HibernateAfterTimeout :: timeout()}
hibernate_after选项,可在通过时启动gen_statem
服务器时使用,enter_loop/4-6
。
如果{hibernate_after,HibernateAfterTimeout}
存在选项,则gen_statem
进程等待任何消息HibernateAfterTimeout
几毫秒,如果没有收到消息,进程将自动进入休眠状态(通过调用proc_lib:hibernate/3
)。
start_opt() =
debug_opt()
|
{timeout, Time :: timeout()} |
hibernate_after_opt()
|
{spawn_opt, [
proc_lib:spawn_option()
]}
gen_statem
例如,通过启动服务器时可以使用的选项start_link/3
。
start_ret() = {ok, pid()} | ignore | {error, term()}
例如,从起始函数返回值start_link/3
。
from() = {To :: pid(), Tag :: term()}
目标在通过例如action()
{reply,From,Reply}
已调用gen_statem
服务器的进程进行回复时使用call/2
。
state() =
state_name()
| term()
如果callback mode
是handle_event_function
,状态可以是任何术语。状态改变(NextState =/= State
)后,所有推迟的事件都会重试。
state_name() = atom()
如果callback mode
是state_functions
,那么国家必须是这种类型的。状态改变(NextState =/= State
)后,所有推迟的事件都会重试。
data() = term()
状态机实现的一个术语是存储它需要的任何服务器数据。这与它state()
本身之间的区别在于,这些数据的变化不会导致延期的事件被重试。因此,如果这些数据的变化会改变所处理事件的集合,那么该数据项将成为状态的一部分。
event_type() =
{call, From ::
from()
} |
cast |
info |
timeout |
{timeout, Name :: term()} |
state_timeout |
internal
外部事件有三种类型:{call,From}
,cast
或info
。Calls
(同步)并casts
源自相应的API函数。对于通话,该事件包含要回复的人。类型info
来自发送给的常规过程消息gen_statem
。状态机实现可以,除了上面,产生events of types
timeout
,{timeout,Name}
,state_timeout
,和internal
自己。
callback_mode_result() =
callback_mode()
| [
callback_mode()
|
state_enter()
]
这是从中Module:callback_mode/0
选择的返回类型callback mode
以及是否执行state enter calls
。
callback_mode() = state_functions | handle_event_function
该回调模式
开始时被选择gen_statem
和使用与该返回值代码改变之后Module:callback_mode/0
。
state_functions
状态必须是state_name()
每个状态的类型和回调函数,即Module:StateName/3
使用。
handle_event_function
该状态可以是任何术语,回调函数Module:handle_event/4
用于所有状态。
state_enter() = state_enter
当使用来自返回值的代码更改和代码更改时,是否选择状态机是否使用状态输入呼叫
。gen_statemModule:callback_mode/0
如果Module:callback_mode/0
返回一个包含的列表state_enter
,gen_statem
引擎会在每次状态更改时调用state callback
with参数(enter, OldState, Data)
。这可能看起来像一个事件,但实际上是在前一个状态回调返回之后以及任何事件传递到新状态回调之前执行的一个调用。见Module:StateName/3
和Module:handle_event/4
。这种调用可以通过从状态回调中返回一个repeat_state
或者一个repeat_state_and_data
元组来重复。
如果Module:callback_mode/0
不返回这样的列表,则没有状态输入呼叫完成。
如果Module:code_change/4
应该将状态转换为具有不同名称的状态,则它仍被视为相同的状态,所以这不会导致状态输入呼叫。
请注意,状态输入呼叫将
在进入初始状态之前完成,即使这正式不是状态更改。在这种情况下,OldState
将
与State
以后的状态更改无法发生的情况相同,但在重复状态输入呼叫时会发生。
transition_option() =
postpone()
|
hibernate()
|
event_timeout()
|
generic_timeout()
|
state_timeout()
转换选项可以通过设置actions
修改状态转换的完成方式:
- 如果状态改变,是初始状态,
repeat_state
或者repeat_state_and_data
被使用,并且也state enter calls
被使用,则gen_statem
使用参数调用新的状态回调(enter, OldState, Data)
。actions
从这个调用返回的任何东西都被处理,就好像它们被追加到状态回调所返回的那些改变状态的动作一样。
- 全部
actions
按照外观顺序进行处理。
- 如果
postpone()
是true
,当前事件被推迟。
- 如果状态改变,传入事件的队列被重置为从最旧的延期开始。
- 存储的所有事件
action()
next_event
插入以在其他排队事件之前进行处理。
- 超时计时器
event_timeout()
,generic_timeout()
并state_timeout()
进行处理。在任何外部未接收事件之前,保证零时间的超时被传送给状态机,所以如果有这样的超时请求,则相应的超时零事件被排队作为最新的事件。
任何事件都会取消,event_timeout()
所以只有在事件队列为空时才会生成零时间事件超时。
状态更改取消了state_timeout()
此类型的任何新转换选项都属于新状态。
- 如果有排队的事件
state callback
,则可能的新状态将与最早排队的事件一起调用,并且我们将从此列表的顶部再次开始。
- 否则,gen_statem进入接收或休眠状态(如果hibernate()为true)等待下一条消息。 在休眠状态下,下一个非系统事件唤醒gen_statem,或者下一个传入的消息唤醒gen_statem,但如果它是系统事件,则会立即进入休眠状态。 当一条新消息到达时,状态回调将与相应的事件一起被调用,并且我们将从该列表的顶部再次开始。
postpone() = boolean()
如果为true
,推迟当前事件并在状态改变时重试(NextState =/= State
)。
hibernate() = boolean()
如果为true
,在进入休眠状态,gen_statem
通过调用proc_lib:hibernate/3
之前进入receive
等待新的外部事件。如果有排队的事件,为了防止接收任何新的事件,可以使用一个erlang:garbage_collect/0
来模拟gen_statem
输入的休眠并立即被最早的排队事件唤醒。
event_timeout() = timeout() | integer()
启动由设置的计时器enter_action()
timeout
。当定时器到期时,event_type()
timeout
将会生成一个事件。看看erlang:start_timer/4
如何Time
和Options
解释。未来erlang:start_timer/4
Options
不一定会得到支持。
任何到达的事件都会取消暂停。请注意,重试或插入的事件计数到达。如果在请求超时之前产生了状态超时零事件,那么也是如此。
如果Time
是infinity
,则不启动计时器,因为它永远不会过期。
如果Time
是相对的,并且0
没有计时器实际启动,而是超时事件被排队以确保它在任何尚未收到的外部事件之前得到处理。
请注意,由于任何其他事件都会自动取消,因此无法取消此超时。
generic_timeout() = timeout() | integer()
启动由设置的计时器enter_action()
{timeout,Name}
。当定时器到期时,event_type()
{timeout,Name}
将会生成一个事件。看看erlang:start_timer/4
如何Time
和Options
解释。未来erlang:start_timer/4
Options
不一定会得到支持。
如果Time
是infinity
,没有计时器启动,因为它永远不会过期。
如果Time
是相对的,并且0
没有计时器实际启动,而是超时事件被排队以确保它在任何尚未收到的外部事件之前得到处理。
Name
在运行时使用相同的定时器将会使用新的超时值重新启动它。因此可以通过设置为取消特定的超时infinity
。
state_timeout() = timeout() | integer()
启动由enter_action()state_timeout设置的计时器。 当定时器到期时,会生成event_type()state_timeout事件。 有关时间和选项如何解释的信息,请参阅erlang:start_timer / 4。 未来的erlang:start_timer / 4选项不一定会被支持。
如果Time
是infinity
,则不启动计时器,因为它永远不会过期。
如果Time
是相对的,并且0
没有计时器实际启动,而是超时事件被排队以确保它在任何尚未收到的外部事件之前得到处理。
在运行时设置此定时器将使用新的超时值重新启动它。因此可以通过设置为取消该超时infinity
。
timeout_option() = {abs, Abs :: boolean()}
如果Abs
是true
一个绝对的计时器开始计时,如果它是false
一个相对的,这是默认的。详情请参阅erlang:start_timer/4
。
action() =
postpone |
{postpone, Postpone ::
postpone()
} |
{next_event,
EventType ::
event_type()
,
EventContent :: term()} |
enter_action()
这些状态转换操作可以通过从state callback
an 回调它们的时候调用event
,Module:init/1
或者通过给它们来调用enter_loop/5,6
。
动作以包含列表的顺序执行。
设置transition options
覆盖任何以前的相同类型的操作,因此包含列表中的最后一个成功。例如,最后一个postpone()
覆盖postpone()
列表中的任何前一个。
postpone
设置transition_option()
postpone()
此状态转换。从返回Module:init/1
或给予时enter_loop/5,6
,此操作将被忽略,因为在这些情况下没有事件可以推迟。
next_event
在执行完所有操作后存储指定的EventType
和EventContent
插入的内容。
将存储的事件插入队列中,作为任何已排队事件之前的下一个处理。保存这些存储事件的顺序,因此第一个next_event
在包含的列表中,将首先处理。
internal
当您想要可靠地区分以任何外部事件插入的事件时,将使用类型事件。
enter_action() =
hibernate |
{hibernate, Hibernate ::
hibernate()
} |
(Timeout ::
event_timeout()
) |
{timeout, Time ::
event_timeout()
, EventContent :: term()} |
{timeout,
Time ::
event_timeout()
,
EventContent :: term(),
Options ::
timeout_option()
| [
timeout_option()
]} |
{{timeout, Name :: term()},
Time ::
generic_timeout()
,
EventContent :: term()} |
{{timeout, Name :: term()},
Time ::
generic_timeout()
,
EventContent :: term(),
Options ::
timeout_option()
| [
timeout_option()
]} |
{state_timeout,
Time ::
state_timeout()
,
EventContent :: term()} |
{state_timeout,
Time ::
state_timeout()
,
EventContent :: term(),
Options ::
timeout_option()
| [
timeout_option()
]} |
reply_action()
这些状态转换操作可以通过从状态回调中返回,从Module:init / 1或通过将它们提供给enter_loop / 5,6来调用。
动作以包含列表的顺序执行。
设置transition options
覆盖任何以前的相同类型的操作,因此包含列表中的最后一个成功。例如,最后一个event_timeout()
覆盖event_timeout()
列表中的任何前一个。
hibernate
设置transition_option()
hibernate()
对于这种状态转换。
Timeout
简称{timeout,Timeout,Timeout}
也就是说,超时信息就是超时时间.。此表单的存在使state callback
返回值{next_state,NextState,NewData,Timeout}
被允许gen_fsm
%27
timeout
设置transition_option()
event_timeout()
到Time
带着EventContent
和超时选项Options
...
{timeout,Name}
设置transition_option()
generic_timeout()
到Time
为Name
带着EventContent
和超时选项Options
...
state_timeout
设置transition_option()
state_timeout()
到Time
带着EventContent
和超时选项Options
...
reply_action() = {reply, From ::
from()
, Reply :: term()}
这个状态转换操作可以通过从Module:init / 1中的状态回调中返回,或者通过将其提供给enter_loop / 5,6来调用。
它回复调用者等待呼叫/ 2中的回复。 From必须是从呼叫中的参数{call,From}到状态回调的术语。
请注意,在Module:init / 1或enter_loop / 5,6中使用这个动作在巫术的边界上会很奇怪,因为在此服务器中没有以前的状态回调调用。
init_result(StateType) =
{ok, State :: StateType, Data ::
data()
} |
{ok,
State :: StateType,
Data ::
data()
,
Actions :: [
action()
] |
action()
} |
ignore |
{stop, Reason :: term()}
对于成功的初始化,State是初始状态(),并且是gen_statem的初始服务器数据()的数据。
当进入第一个状态时,就像状态回调一样,动作被执行,除了由于没有要延期的事件,动作延迟被强制为假。
对于无法成功的初始化,{stop,Reason}
或ignore
应使用;见start_link/3,4
...
state_enter_result(State) =
{next_state, State, NewData ::
data()
} |
{next_state,
State,
NewData ::
data()
,
Actions :: [
enter_action()
] |
enter_action()
} |
state_callback_result
(
enter_action()
)
State
是当前状态,并且它不能更改,因为状态回调是用state enter call
...
next_state
该gen_statem
做的状态过渡到State
,它必须是当前的状态,设置NewData
,并执行所有Actions
。
event_handler_result(StateType) =
{next_state, NextState :: StateType, NewData ::
data()
} |
{next_state,
NextState :: StateType,
NewData ::
data()
,
Actions :: [
action()
] |
action()
} |
state_callback_result
(
action()
)
StateType
是state_name()
如果callback mode
是state_functions
,或state()
如果callback mode
是handle_event_function
...
next_state
gen_statem执行到NextState的状态转换(可以与当前状态相同),设置NewData并执行所有Actions。
state_callback_result(ActionType) =
{keep_state, NewData ::
data()
} |
{keep_state,
NewData ::
data()
,
Actions :: [ActionType] | ActionType} |
keep_state_and_data |
{keep_state_and_data, Actions :: [ActionType] | ActionType} |
{repeat_state, NewData ::
data()
} |
{repeat_state,
NewData ::
data()
,
Actions :: [ActionType] | ActionType} |
repeat_state_and_data |
{repeat_state_and_data, Actions :: [ActionType] | ActionType} |
stop |
{stop, Reason :: term()} |
{stop, Reason :: term(), NewData ::
data()
} |
{stop_and_reply,
Reason :: term(),
Replies :: [
reply_action()
] |
reply_action()
} |
{stop_and_reply,
Reason :: term(),
Replies :: [
reply_action()
] |
reply_action()
,
NewData ::
data()
}
ActionType是enter_action(),如果状态回调是通过一个状态enter call和action()来调用的,如果状态回调是用一个事件调用的。
keep_state
gen_statem保持当前状态,或者如果你愿意,状态转换到当前状态,设置NewData并执行所有的Actions。 这与{next_state,CurrentState,NewData,Actions}相同。
keep_state_and_data
gen_statem保持当前状态,或者如果你愿意,状态转换到当前状态,保持当前的服务器数据并执行所有的动作。 这与{next_state,CurrentState,CurrentData,Actions}相同。
repeat_state
gen_statem保持当前状态,或者如果你愿意,状态转换到当前状态,设置NewData并执行所有的Actions。 如果gen_statem以状态输入调用运行,则会重复进入状态输入调用,请参阅type transition_option(),否则repeat_state与keep_state相同。
repeat_state_and_data
gen_statem保持当前状态和数据,或者如果你愿意,状态转换到当前状态,并执行所有的动作。 这与{repeat_state,CurrentData,Actions}相同。 如果gen_statem以状态输入调用运行,则重复进入状态输入调用,请参见类型transition_option(),否则repeat_state_and_data与keep_state_and_data相同。
stop
终止gen_statem
通过调用Module:terminate/3
与Reason
和NewData
,如果指定。
stop_and_reply
发送所有回复,然后通过使用Reason和NewData(如果指定)调用Module:terminate / 3来终止gen_statem。
所有这些术语都是元组或原子,此属性将在未来版本的gen_statem
...
输出
call(ServerRef :: server_ref(), Request :: term()) ->
Reply :: term()
call(ServerRef :: server_ref()
,
Request :: term(),
超时::
timeout() |
{clean_timeout, T :: timeout()} |
{dirty_timeout, T :: timeout()}) ->
Reply :: term()
通过发送请求并等待其回复到达,对gen_statem ServerRef进行同步调用。 gen_statem使用event_type(){call,From}和事件内容Request调用状态回调。
当状态回调以{reply,From,Reply}作为一个action()返回时生成一个Reply,并且该Reply成为此函数的返回值。
Timeout
是一个大于0的整数,它指定等待答复的毫秒数,或infinity
默认情况下无限等待原子的毫秒数。如果在指定的时间内没有收到回复,则函数调用失败。
注
因为Timeout < infinity如果调用者应该捕获异常,为了避免在调用者的收件箱中收到延迟回复,该函数会生成一个代理进程来完成调用。一个迟到的回复被传递给死了的代理进程,因此被丢弃。这比使用效率低Timeout == infinity。
Timeout也可以是元组{clean_timeout,T}或{dirty_timeout,T},在哪里T就是暂停时间。{clean_timeout,T}就像T在上面的说明中描述,并使用代理进程T < infinity,同时{dirty_timeout,T}绕过更轻量级的代理进程。
注
如果将此函数的捕获异常与{dirty_timeout,T}
为了避免调用过程在调用超时时死亡,您必须准备好处理延迟的回复。那么,为什么不让调用进程死掉呢?
调用也可能失败,例如,如果gen_statem
在此函数调用之前或期间死亡。
cast(ServerRef :: server_ref(), Msg :: term()) -> ok
将异步事件发送到gen_statemServerRef
和返回ok
立即忽略目标节点或gen_statem
不存在。大gen_statem
调用state callback
带着event_type()cast
和事件内容Msg
...
enter_loop(Module :: module(),
Opts :: [debug_opt()
| hibernate_after_opt()
],
State :: state()
,
Data :: data()) ->
no_return()
与使用Actions = []的enter_loop / 6相同,除非没有server_name()必须已注册。 这创建了一个匿名服务器。
enter_loop(Module :: module(),
Opts :: [debug_opt()
| hibernate_after_opt()
],
State :: state()
,
Data :: data()
,
Server_or_Actions :: server_name() | pid() | [action()]) ->
no_return()
如果Server_or_Actions是一个list(),与enter_loop / 6相同,只是没有server_name()必须已经注册,Actions = Server_or_Actions。 这创建了一个匿名服务器。
否则,与server = Server_or_Actions和Actions = []时的enter_loop / 6相同。
enter_loop(Module :: module(),
Opts :: [debug_opt()
| hibernate_after_opt()
],
State :: state()
,
Data :: data()
,
Server :: server_name()
| pid(),
Actions :: [action()] | action()) ->
no_return()
使调用过程成为gen_statem。 不返回,而是调用进程进入gen_statem接收循环并成为gen_statem服务器。 该进程必须使用proc_lib中的一个启动函数启动。 用户负责该过程的任何初始化,包括为其注册一个名称。
当需要比gen_statem行为提供的更复杂的初始化过程时,此函数非常有用。
Module
,Opts
的含义调用时相同的start[_link]/3,4
。
如果Server
是self()
一个匿名服务器就像使用时一样创建start[_link]/3
。如果Server
是一个server_name()
命名服务器就像使用时一样创建start[_link]/4
。但是,在调用该函数之前
,该server_name()
名称必须已经相应注册。
State
,Data
和Actions
具有与返回值相同的含义Module:init/1
。此外,回调模块不需要导出Module:init/1
功能。
如果调用过程未由proc_lib
启动函数启动,或者未按照注册过程启动,则函数server_name()
失败。
reply(Replies :: [reply_action()] | reply_action()) -> ok
reply(From :: from(), Reply :: term()) -> ok
gen_statem可以使用此函数将回复显式地发送给等待调用/ 2的进程,此时不能在状态回调的返回值中定义回复。
From必须是从参数{call,From}到状态回调的术语。 还可以使用一个或多个来自状态回调的reply_action()来发送回复或多个回复。
注
在sys调试输出中不可见使用此函数发送的答复。
start(Module :: module(), Args :: term(), Opts :: [start_opt()]) ->
start_ret()
start(ServerName :: server_name()
,
Module :: module(),
Args :: term(),
Opts :: [start_opt()]) ->
start_ret()
gen_statem
根据OTP设计原则创建独立进程(使用proc_lib
原语)。由于它没有链接到调用过程,因此主管不能使用此启动功能来启动一个孩子。
有关参数和返回值的说明,请参见start_link/3,4
...
start_link(Module :: module(),
Args :: term(),
选择:[start_opt()]%29->
start_ret()
start_link(ServerName :: server_name()
,
Module :: module(),
Args :: term(),
Opts :: [start_opt()]) ->
start_ret()
根据与调用进程相关联的OTP设计原则(使用proc_lib基元)创建gen_statem进程。 当gen_statem必须是监督树的一部分时,这是非常重要的,所以它必须链接到它的主管。
gen_statem进程调用Module:init / 1来初始化服务器。 为了确保同步启动过程,直到Module:init / 1返回后,start_link / 3,4才会返回。
ServerName指定要注册gen_statem的server_name()。 如果gen_statem以start_link / 3启动,则不提供ServerName,并且gen_statem未注册。
Module
是回调模块的名称。
Args
作为参数传递给Module:init/1
...
- 如果在Opts中存在选项{timeout,Time},则允许gen_statem花费时间毫秒初始化或终止,并且启动函数返回{error,timeout}。
- 如果选项{hibernate_after,HibernateAfterTimeout}存在,则gen_statem进程将等待HibernateAfterTimeout毫秒的任何消息,并且如果没有收到消息,则进程会自动进入休眠状态(通过调用proc_lib:hibernate / 3)。
- 如果选项{debug,Dbgs}存在于Opts中,则通过sys进行调试将被激活。
- 如果选项{spawn_opt,SpawnOpts}存在于Opts中,SpawnOpts作为选项列表传递给erlang:spawn_opt / 2,用于产生gen_statem进程。
注
使用spawn选项monitor
是不允许的,它会导致这个函数失败并带有原因badarg
。
如果gen_statem
成功创建和初始化,这个函数返回{ok,Pid}
,这里Pid
是pid()
的gen_statem
。如果与所指定的处理ServerName
已经存在,这个函数返回{error,{already_started,Pid}}
,这里Pid
是pid()
这一进程的。
如果Module:init/1
失败Reason
,则此函数返回{error,Reason}
。如果Module:init/1
返回{stop,Reason}
或者ignore
,进程终止并且这个函数返回{error,Reason}
或者ignore
分别。
stop(ServerRef :: server_ref()) -> ok
和stop(ServerRef, normal, infinity)
一样。
stop(ServerRef :: server_ref()
,
Reason :: term(),
Timeout :: timeout()) ->
ok
订单gen_statem ServerRef以指定的原因退出并等待它终止。 gen_statem在退出之前调用Module:terminate / 3。
如果服务器以预期的原因终止,则此函数返回OK。 除正常,关机或{shutdown,Term}之外的任何其他原因都会导致通过error_logger:format / 2发出错误报告。 默认原因是正常的。
Timeout
是一个大于0的整数,它指定等待服务器终止多少毫秒,或者infinity
无限期等待原子。默认为infinity
。如果服务器在指定时间内没有终止,则会引发异常timeout
。
如果进程不存在,则引发异常noproc
。
回调函数
下面的函数将从gen_statem
回调模块。
输出
Module:callback_mode() -> CallbackMode
类型
当需要找出回调模块的回调模式时,该函数由gen_statem调用。 由于效率原因,该值由gen_statem缓存,所以此函数仅在服务器启动后和代码更改后调用一次,但在调用当前代码版本的第一个状态回调之前调用。 未来版本的gen_statem可能会添加更多的场合。
当Module:init / 1返回或调用了enter_loop / 4-6时,服务器启动会发生。 代码更改发生在Module:code_change / 4返回时。
CallbackMode或者只是callback_mode()或者一个包含callback_mode()和可能的原子state_enter的列表。
注
如果这个函数的主体没有返回一个内联常量值,那么回调模块会做一些奇怪的事情。
Module:code_change(OldVsn, OldState, OldData, Extra) -> Result
类型
注
此回调是可选的,因此回调模块不需要导出它。 如果在未执行code_change / 4时执行.appup文件中指定的Change = {advanced,Extra}版本的升级/降级,则该流程将因退出原因undef而崩溃。
在发布升级/降级期间更新其内部状态时,也就是说,当{update,Module,Change,...}(其中Change = {advanced,Extra})的内容更新时,由gen_statem调用此函数 在appup文件中指定。 有关更多信息,请参阅OTP设计原则。
对于升级,OldVsn是Vsn,对于降级,OldVsn是{down,Vsn}。 Vsn由回调模块Module的旧版本的vsn属性定义。 如果未定义此类属性,则版本是Beam文件的校验和。
OldState
与OldData
是内部状态gen_statem
。
Extra
从{advanced,Extra}
更新指令的部分按原样传递。
如果成功,该函数必须返回{ok,NewState,NewData}
元组中更新的内部状态。
如果函数返回失败原因,正在进行的升级失败并回滚到旧版本。 请注意,Reason不能是{ok,_,_}元组,因为这将被视为{ok,NewState,NewData}元组,并且匹配{ok,_}的元组也是无效的失败原因。 建议使用原子作为原因,因为它将被封装在{error,Reason}元组中。
还要注意,升级gen_statem
时此函数以及文件中的Change={advanced,Extra}
参数appup
不仅需要更新内部状态或作用于Extra
参数。如果升级或降级应该改变callback mode
,还需要这样做,否则代码更改后的回调模式将不被遵守,最有可能导致服务器崩溃。
Module:init(Args) -> Result(StateType)
类型
每当 gen_statem
开始使用start_link/3,4
or时start/3,4
,新函数就会调用该函数来初始化实现状态和服务器数据。
Args
是Args
参数提供给该开始函数。
注
请注意,如果gen_statem
启动槽proc_lib
和enter_loop/4-6
,这个回调将不会被调用。由于该回调不是可选的,因此在这种情况下可以实现为:
init(Args) -> erlang:error(not_implemented, [Args]).
Module:format_status(Opt, [PDict,State,Data]) -> Status
类型
注
这个回调是可选的,所以回调模块不需要导出它。该gen_statem
模块提供了返回该函数的默认实现{State,Data}
。
如果此回调被导出但失败,为了隐藏可能的敏感数据,默认函数将返回{State,Info}
,除非Infoformat_status/2
已经崩溃,否则将返回。
此函数由gen_statem
当下列任何一项应用时,处理:
- 其中一个
sys:get_status/1,2
被调用来获得gen_statem
状态。Opt
被设置normal
为这种情况下的原子。
- 该
gen_statem
异常终止,记录一个错误。Opt
被设置terminate
为这种情况下的原子。
此功能对于更改gen_statem
这些案例的状态和外观非常有用。希望更改sys:get_status/1,2
返回值以及其状态在终止错误日志中的显示方式的回调模块会导出一个实例format_status/2
,该实例将返回一个描述当前状态的术语gen_statem
。
PDict
是过程字典的当前值gen_statem
。
State
是的内部状态gen_statem
。
Data
是的内部服务器数据gen_statem
。
函数返回Status
一个包含当前状态和状态的适当细节的术语gen_statem
。上有没有形式限制Status
可以采取,但对于sys:get_status/1,2
情况下(当Opt
是normal
),对于推荐的形式Status
值[{data, [{"State", Term}]}]
,其中Term
提供的相关细节gen_statem
状态。遵循此建议不是必需的,但它使回调模块状态与sys:get_status/1,2
返回值的其余部分保持一致。
此功能的一个用途是返回紧凑的替代状态表示,以避免在日志文件中打印较大的状态项。另一个用途是隐藏敏感数据写入错误日志。
Module:StateName(enter, OldState, Data) -> StateEnterResult(StateName)Module:StateName(EventType, EventContent, Data) -> StateFunctionResultModule:handle_event(enter, OldState, State, Data) -> StateEnterResult(State)Module:handle_event(EventType, EventContent, State, Data) -> HandleEventResult
类型
每当gen_statem从call / 2,cast / 2接收一个事件,或者作为一个普通的进程消息,就会调用其中一个函数。 如果回调模式是state_functions,则调用Module:StateName / 3,如果是handle_event_function,则调用Module:handle_event / 4。
如果EventType
是{call,From}
,主叫方等待回复。答复可以state callback
通过{reply,From,Reply}
在Actions
,在Replies
,或通过呼叫返回或从任何其他人发送reply(From, Reply)
。
如果此函数返回下一个状态,=/=
该状态与当前状态的equals()不匹配,则在下一个状态中重试所有推迟的事件。
StateFunctionResult和HandleEventResult之间的唯一区别是,对于StateFunctionResult,下一个状态必须是原子,但对于HandleEventResult,下一个状态没有限制。
对于可以设置的选项以及gen_statem
从此功能返回后可以执行的操作,请参阅action()
。
当gen_statem以状态输入调用运行时,只要状态改变,这些函数也可以用参数调用(输入OldState ...)。在这种情况下,可能会返回的操作有一些限制:由于状态输入调用不是事件,所以不允许使用postpone(),因此没有要推迟的事件,并且{next_event,_,_}是不允许的使用状态输入呼叫不应该影响事件的消耗和生成方式。您也可能不会更改此通话的状态。如果你使用NextState = / =返回{next_state,NextState,...},则说明gen_statem崩溃。有可能使用{repeat_state,...},{repeat_state_and_data,_}或repeat_state_and_data,但它们都没什么意义,因为您立即再次调用新的状态输入调用,这使得这只是一种奇怪的循环方式,而且有更好的方式来循环Erlang。建议您使用{keep_state,...},{keep_state_and_data,_}或keep_state_and_data,因为无论如何您都无法从状态输入调用更改状态。
请注意,您可以使用throw返回结果,这很有用。 例如,在复杂代码中使用throw(keep_state_and_data)进行救援,因为状态或数据不再处于范围内,所以无法返回{next_state,State,Data}。
Module:terminate(Reason, State, Data) -> Ignored
类型
注
这个回调是可选的,所以回调模块不需要导出它。大gen_statem
模块提供默认实现而不进行清理。
这个函数gen_statem
在它即将终止时被调用。这是相反的,Module:init/1
并做了任何必要的清理。当它返回时,gen_statem
终止Reason
。返回值被忽略。
Reason
是一个表示停止原因的术语,State
是内部状态gen_statem
。
Reason
取决于为什么gen_statem
终止。如果是因为另一个回调函数返回,停止元组{stop,Reason}
中Actions
,Reason
具有这样的元组指定的值。如果是因为失败,Reason
是错误原因。
如果gen_statem
是监督树的一部分并且由其主管下令终止,Reason = shutdown
则在以下条件适用的情况下调用此函数:
- 在
gen_statem
已设置为捕获退出信号。
- 主管的子规范中定义的关闭策略是整数超时值,而不是
brutal_kill
。
即使gen_statem
它不是
监督树的一部分,如果它'EXIT'
从其父代收到一条消息,该函数也会被调用。Reason
与'EXIT'
消息中的相同。
否则,gen_statem
立即终止。
请注意,对于任何其它原因normal
,shutdown
或者{shutdown,Term}
,将gen_statem
被认为终止,因为一个错误和错误报告被出具使用error_logger:format/2
。
另见
gen_event(3)
,gen_fsm(3)
,gen_server(3)
,proc_lib(3)
,supervisor(3)
,sys(3)
...