Erlang 20

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 modehandle_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/3server_ref()下面。

server_ref() =

pid() |

(LocalName :: atom()) |

{Name :: atom(), Node :: atom()} |

{global, GlobalName :: term()} |

{via, RegMod :: module(), ViaName :: term()}

当寻址时使用的服务器规范gen_statem服务器。见call/2server_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

对于每个输入项Dbgssys都调用相应的函数。

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 modehandle_event_function,状态可以是任何术语。状态改变(NextState =/= State)后,所有推迟的事件都会重试。

state_name() = atom()

如果callback modestate_functions,那么国家必须是这种类型的。状态改变(NextState =/= State)后,所有推迟的事件都会重试。

data() = term()

状态机实现的一个术语是存储它需要的任何服务器数据。这与它state()本身之间的区别在于,这些数据的变化不会导致延期的事件被重试。因此,如果这些数据的变化会改变所处理事件的集合,那么该数据项将成为状态的一部分。

event_type() =

{call, From ::from()} |

cast |

info |

timeout |

{timeout, Name :: term()} |

state_timeout |

internal

外部事件有三种类型:{call,From}castinfoCalls(同步)并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_entergen_statem引擎会在每次状态更改时调用state callbackwith参数(enter, OldState, Data)。这可能看起来像一个事件,但实际上是在前一个状态回调返回之后以及任何事件传递到新状态回调之前执行的一个调用。见Module:StateName/3Module:handle_event/4。这种调用可以通过从状态回调中返回一个repeat_state或者一个repeat_state_and_data元组来重复。

如果Module:callback_mode/0不返回这样的列表,则没有状态输入呼叫完成。

如果Module:code_change/4应该将状态转换为具有不同名称的状态,则它仍被视为相同的状态,所以这不会导致状态输入呼叫。

请注意,状态输入呼叫在进入初始状态之前完成,即使这正式不是状态更改。在这种情况下,OldStateState以后的状态更改无法发生的情况相同,但在重复状态输入呼叫时会发生。

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如何TimeOptions解释。未来erlang:start_timer/4 Options不一定会得到支持。

任何到达的事件都会取消暂停。请注意,重试或插入的事件计数到达。如果在请求超时之前产生了状态超时零事件,那么也是如此。

如果Timeinfinity,则不启动计时器,因为它永远不会过期。

如果Time是相对的,并且0没有计时器实际启动,而是超时事件被排队以确保它在任何尚未收到的外部事件之前得到处理。

请注意,由于任何其他事件都会自动取消,因此无法取消此超时。

generic_timeout() = timeout() | integer()

启动由设置的计时器enter_action() {timeout,Name}。当定时器到期时,event_type() {timeout,Name}将会生成一个事件。看看erlang:start_timer/4如何TimeOptions解释。未来erlang:start_timer/4 Options不一定会得到支持。

如果Timeinfinity,没有计时器启动,因为它永远不会过期。

如果Time是相对的,并且0没有计时器实际启动,而是超时事件被排队以确保它在任何尚未收到的外部事件之前得到处理。

Name在运行时使用相同的定时器将会使用新的超时值重新启动它。因此可以通过设置为取消特定的超时infinity

state_timeout() = timeout() | integer()

启动由enter_action()state_timeout设置的计时器。 当定时器到期时,会生成event_type()state_timeout事件。 有关时间和选项如何解释的信息,请参阅erlang:start_timer / 4。 未来的erlang:start_timer / 4选项不一定会被支持。

如果Timeinfinity,则不启动计时器,因为它永远不会过期。

如果Time是相对的,并且0没有计时器实际启动,而是超时事件被排队以确保它在任何尚未收到的外部事件之前得到处理。

在运行时设置此定时器将使用新的超时值重新启动它。因此可以通过设置为取消该超时infinity

timeout_option() = {abs, Abs :: boolean()}

如果Abstrue一个绝对的计时器开始计时,如果它是false一个相对的,这是默认的。详情请参阅erlang:start_timer/4

action() =

postpone |

{postpone, Postpone ::postpone()} |

{next_event,

EventType ::event_type(),

EventContent :: term()} |

enter_action()

这些状态转换操作可以通过从state callbackan 回调它们的时候调用eventModule:init/1或者通过给它们来调用enter_loop/5,6

动作以包含列表的顺序执行。

设置transition options覆盖任何以前的相同类型的操作,因此包含列表中的最后一个成功。例如,最后一个postpone()覆盖postpone()列表中的任何前一个。

postpone

设置transition_option() postpone()此状态转换。从返回Module:init/1或给予时enter_loop/5,6,此操作将被忽略,因为在这些情况下没有事件可以推迟。

next_event

在执行完所有操作后存储指定的EventTypeEventContent插入的内容。

将存储的事件插入队列中,作为任何已排队事件之前的下一个处理。保存这些存储事件的顺序,因此第一个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()TimeName带着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())

StateTypestate_name()如果callback modestate_functions,或state()如果callback modehandle_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/3ReasonNewData,如果指定。

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行为提供的更复杂的初始化过程时,此函数非常有用。

ModuleOpts的含义调用时相同的start[_link]/3,4

如果Serverself()一个匿名服务器就像使用时一样创建start[_link]/3。如果Server是一个server_name()命名服务器就像使用时一样创建start[_link]/4。但是,在调用该函数之前,该server_name()名称必须已经相应注册。

StateDataActions具有与返回值相同的含义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},这里Pidpid()gen_statem。如果与所指定的处理ServerName已经存在,这个函数返回{error,{already_started,Pid}},这里Pidpid()这一进程的。

如果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文件的校验和。

OldStateOldData是内部状态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,4or时start/3,4,新函数就会调用该函数来初始化实现状态和服务器数据。

ArgsArgs参数提供给该开始函数。

请注意,如果gen_statem启动槽proc_libenter_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情况下(当Optnormal),对于推荐的形式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}ActionsReason具有这样的元组指定的值。如果是因为失败,Reason是错误原因。

如果gen_statem是监督树的一部分并且由其主管下令终止,Reason = shutdown则在以下条件适用的情况下调用此函数:

  • gen_statem已设置为捕获退出信号。

  • 主管的子规范中定义的关闭策略是整数超时值,而不是brutal_kill

即使gen_statem不是监督树的一部分,如果它'EXIT'从其父代收到一条消息,该函数也会被调用。Reason'EXIT'消息中的相同。

否则,gen_statem立即终止。

请注意,对于任何其它原因normalshutdown或者{shutdown,Term},将gen_statem被认为终止,因为一个错误和错误报告被出具使用error_logger:format/2

另见

gen_event(3)gen_fsm(3)gen_server(3)proc_lib(3)supervisor(3)sys(3)...