查看源码 gen_statem 行为 (stdlib v6.2)
通用状态机行为。
gen_statem 提供了一个通用的状态机行为,自 Erlang/OTP 20.0 起取代了它的前身 gen_fsm,应该用于新代码。gen_fsm 行为仍然保留在 OTP 中“原样”,以避免破坏使用它的旧代码。
使用此模块实现的通用状态机服务器进程 (gen_statem) 具有一组标准的接口函数,并包括跟踪和错误报告的功能。它也适合 OTP 监管树。有关更多信息,请参阅 OTP 设计原则。
注意
如果您是
gen_statem的新手,并且想要了解概念和操作的概述,建议阅读位于用户指南 OTP 设计原则 中的gen_statem行为 部分。本参考手册侧重于正确和完整,这可能会让人难以看到全局。
特性
gen_statem 具有 gen_fsm 所具有的相同特性,并添加了一些非常有用的特性
支持两种 回调模式
state_functions- 用于有限状态机(类似gen_fsm),这要求状态是一个原子,并将该状态用作当前回调函数的名称,arity 为 3。handle_event_function- 允许状态为任何项,并使用handle_event/4作为所有状态的回调函数。
gen_statem 的回调模式与 gen_fsm 的不同,但是仍然很容易从 gen_fsm 重写为 gen_statem。请参阅 gen_fsm 文档开头的 重写指南。
回调模块
gen_statem 假设所有特定部分都位于回调模块中,该模块导出了一组预定义的函数。行为函数和回调函数之间的关系如下
gen_statem module Callback module
----------------- ---------------
gen_statem:start
gen_statem:start_monitor
gen_statem:start_link -----> Module:init/1
Server start or code change
-----> Module:callback_mode/0
selects callback mode
gen_statem:stop
Supervisor exit
Callback failure -----> Module:terminate/3
gen_statem:call
gen_statem:cast
gen_statem:send_request
erlang:send
erlang:'!' -----> Module:StateName/3
or -----> Module:handle_event/4
depending on callback mode
Release upgrade/downgrade
(code change)
-----> Module:code_change/4状态回调
gen_statem 中特定 状态 的状态回调是在此状态中为所有事件调用的回调函数。它根据回调模块使用回调函数 Module:callback_mode/0 定义的 回调模式 来选择。
当 回调模式 为 state_functions 时,状态必须是一个原子,并用作状态回调名称;请参阅 Module:StateName/3。这会将特定状态的所有代码定位在一个函数中,因为 gen_statem 引擎会根据状态名称进行分支。请注意,回调函数 Module:terminate/3 使状态名称 terminate 在此模式下不可用。
当 回调模式 为 handle_event_function 时,状态可以是任何项,状态回调名称为 Module:handle_event/4。这使得可以根据需要轻松地根据状态或事件进行分支。请注意您在哪些状态中处理哪些事件,以免意外地永久延迟事件而创建无限的繁忙循环。
事件类型
事件具有不同的 类型,因此回调函数在处理事件时可以知道事件的来源。外部事件 是 call、cast 和 info。内部事件是 timeout 和 internal。
事件处理
当 gen_statem 接收到进程消息时,它会转换为一个事件,并使用该事件作为两个参数:类型和内容来调用状态回调。当 状态回调 处理完事件后,它会返回到 gen_statem,后者会执行状态转换。如果此状态转换是到不同的状态,即:NextState =/= State,则这是一个状态更改。
转换操作
状态回调 可以返回 转换操作,供 gen_statem 在状态转换期间执行,例如设置超时或回复调用。
回复调用
有关如何回复调用,请参阅 gen_statem:call/2,3。可以从任何状态回调发送回复,而不仅仅是接收到请求事件的回调。
事件延迟
一个可能的转换操作是延迟当前事件。然后它不会在当前状态中处理。 gen_statem 引擎维护一个事件队列,该队列分为延迟的事件和仍要处理的事件(尚未呈现)。在状态更改之后,队列会从延迟的事件重新开始。
gen_statem 事件队列模型足以使用选择性接收来模拟正常的进程消息队列。延迟事件相当于在接收语句中不匹配它,而更改状态相当于进入新的接收语句。
事件插入
状态回调 可以使用 转换操作 next_event 插入事件,并且这样的事件会插入到事件队列中,作为下一个调用 状态回调 的事件。也就是说,就好像它是最老的传入事件一样。专用的 event_type/0 internal 可以用于此类事件,使其可以安全地将其与外部事件区分开。
插入事件取代了调用您自己的状态处理函数的技巧,例如,您通常必须在 gen_fsm 中使用该技巧,以强制在其他事件之前处理插入的事件。
注意
如果您延迟了一个事件,并且(违反良好实践)直接调用了不同的状态回调,则延迟的事件不会重试,因为没有状态更改。
不要直接调用状态回调,而是执行状态更改。这使得
gen_statem引擎可以重试延迟的事件。在状态更改中插入事件还会触发新的状态回调,以便在接收任何外部事件之前使用该事件进行调用。
状态进入调用
每当进入新状态时,gen_statem 引擎可以自动对 状态回调 进行特殊调用;请参阅 state_enter/0。这是为了编写所有状态条目通用的代码。另一种方法是在状态转换时显式插入事件,和/或使用专用的状态转换函数,但这需要在每次状态转换到需要它的状态时记住。
有关状态转换的详细信息,请参阅类型 transition_option/0。
休眠
gen_statem 进程可以进入休眠状态;请参阅 proc_lib:hibernate/3。当 状态回调 或 Module:init/1 在返回的 Actions 列表中指定 hibernate 时,就会执行此操作。当服务器预计长时间处于空闲状态时,此功能可用于回收进程堆内存。但是,请谨慎使用,因为休眠可能过于昂贵,不适合在每次事件后使用;请参阅 erlang:hibernate/3。
还有服务器启动选项 {hibernate_after, Timeout} 用于 start/3,4、start_link/3,4、start_monitor/3,4 或 enter_loop/4,5,6,可用于自动休眠服务器。
回调失败
如果回调函数失败或返回错误值,则 gen_statem 将终止。但是,类 throw 的异常不被视为错误,而是所有回调函数的有效返回。
系统消息和 sys 模块
gen_statem 按照 sys 中的描述处理系统消息。 sys 模块可用于调试 gen_statem。通过 转换操作 发送的回复会被记录,但通过 reply/1,2 发送的回复不会被记录。
捕获退出
像所有 gen_* 行为一样,gen_statem 进程不会自动捕获退出信号;这必须在回调模块中显式启动(通过调用 process_flag(trap_exit, true),最好从 init/1)。
服务器终止
如果 gen_statem 进程终止,例如由于回调函数返回 {stop, Reason} 的结果,则会向链接的进程和端口发送带有此 Reason 的退出信号。有关使用退出信号进行错误处理的详细信息,请参阅参考手册中的 进程。
注意
有关分布式信号的一些重要信息,请参阅 Erlang 参考手册 的进程章节中的 通过分布式阻塞信号 部分。阻塞信号可能会导致
gen_statem中的调用超时被显著延迟。
错误参数
除非另有说明,否则如果指定的 gen_statem 不存在或指定了错误的参数,则此模块中的所有函数都会失败。
示例
以下示例显示了一个简单的按钮模型,用于实现使用 回调模式 state_functions 实现的切换按钮。您可以按下按钮,它会回复它是打开还是关闭,并且您可以询问它被按下多少次以打开的计数。
按钮状态图
---
title: Pushbutton State Diagram
---
stateDiagram-v2
[*] --> off
off --> on : push\n* Increment count\n* Reply 'on'
on --> off : push\n* Reply 'off'状态图中未显示
- API 函数
push()生成一个类型为call的push事件。 - API 函数
get_count()生成一个get_count事件,类型为call,该事件在所有状态下通过回复当前计数器值进行处理。 - 未知的事件将被忽略并丢弃。
- 存在用于启动、停止、终止、代码更改、初始化等的样板代码,用于将回调模式设置为
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)为了比较样式,下面是使用 回调模式 handle_event_function 的相同示例,或者更确切地说,是在 pushbutton.erl 示例文件中的 init/1 函数之后要替换的代码
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}.注意
API 变更
- 此行为在 Erlang/OTP 19.0 中作为实验性功能出现。
- 在 OTP 19.1 中,对
Module:init/1的返回元组进行了向后不兼容的更改,引入了强制性回调函数Module:callback_mode/0,并添加了enter_loop/4。- 在 OTP 19.2 中,添加了 状态进入调用。
- 在 OTP 19.3 中,添加了状态超时。
- 在 OTP 20.0 中,添加了通用超时,并声明
gen_statem不再是实验性的,并且优于gen_fsm。- 在 OTP 22.1 中,添加了超时内容
update和显式超时cancel。- 在 OTP 22.3 中,添加了使用动作
change_callback_module、push_callback_module和pop_callback_module更改回调模块的可能性。- 在 OTP 23.0 中,添加了
start_monitor/3,4,以及用于异步调用的函数:send_request/2、wait_response/1,2和check_response/2。- 在 OTP 24.0 中,添加了
receive_response/1,2。- 在 OTP 25.0 中,添加了
Module:format_status/1来替换Module:format_status/1,以及用于异步调用集合的函数:send_request/4、wait_response/3、receive_response/3、check_response/3、reqids_new/0、reqids_size/1、reqids_add/3、reqids_to_list/1。- 在 OTP 26.0 中,添加了从
Module:init/1返回{error, Reason}的可能性。- 在 OTP 27.0 中,
Module:format_status/1被弃用。
另请参阅
摘要
类型
状态转换时的操作,或启动服务器时的操作。
每个状态一个函数或一个通用的事件处理程序。
服务器的通用状态数据。
任何回调的操作:休眠、超时或回复。
从事件的来源发送到状态回调的事件有效负载。
处理事件后来自状态回调的返回值。
等待事件的时间。
描述服务器状态的映射。
等待命名超时事件的时间。
使服务器进程休眠。
推迟事件以便稍后处理。
将回复与相应请求关联的句柄。
不透明的请求标识符。有关详细信息,请参阅 send_request/2。
不透明的请求标识符 (request_id/0) 集合。
异步调用的响应超时。
服务器名称规范:local、global 或 via 注册。
服务器规范:pid/0 或已注册的 server_name/0。
来自 start_monitor/3,4 函数的返回值。
用于 start/3,4、start_link/3,4 和 start_monitor/3,4 函数的服务器启动选项。
来自 start/3,4 和 start_link/3,4 函数的 返回值。
状态名称或状态术语。
来自任何 状态回调 的返回值。
状态进入调用的回调模式 修饰符:原子 state_enter。
在状态进入调用之后,来自状态回调的返回值。
在 回调模式 state_functions 中的状态名称。
在当前状态下等待的时间。
事件超时、通用超时或状态超时。
比将其设置为“无限”更清晰地取消超时的方法。
超时计时器启动选项,用于选择过期绝对时间。
更新 EventContent,而不影响过期时间。
由 操作设置的状态转换选项。
函数
调用服务器:发送请求并等待响应。
向服务器发送一个事件。
检查接收到的消息是否为请求响应。
检查接收到的消息是否为集合中的请求响应。
使调用进程成为 gen_statem 服务器。
使调用进程成为 gen_statem 服务器。
接收请求响应。
发送一个或多个 call 回复。
将 call Reply 发送到 From。
将请求标识符存储在集合中。
创建一个空的请求标识符集合。
返回 ReqIdCollection 中请求标识符的数量。
将请求标识符集合转换为列表。
发送异步 call 请求。
发送异步 call 请求并将其添加到请求标识符集合中。
启动服务器,既不链接也不注册。
启动服务器,已注册但未链接。
启动服务器,已链接但未注册。
启动服务器,已链接和注册。
启动服务器,已监视但既不链接也不注册。
启动服务器,已监视和注册,但未链接。
等待请求响应。
等待集合中任何请求的响应。
类型
-type action() :: postpone | {postpone, Postpone :: postpone()} | {next_event, EventType :: event_type(), EventContent :: event_content()} | {change_callback_module, NewModule :: module()} | {push_callback_module, NewModule :: module()} | pop_callback_module | enter_action().
状态转换时的操作,或启动服务器时的操作。
这些转换动作可以通过从状态回调中返回它们来调用,当使用事件调用时,从Module:init/1返回,或者通过将它们传递给enter_loop/4,5,6来调用。它们在状态进入调用中是不允许的。
动作按照包含列表的顺序执行。
设置转换选项的动作会覆盖任何先前相同类型的动作,因此包含列表中的最后一个动作胜出。例如,最后一个postpone/0会覆盖列表中任何先前的postpone/0。
{postpone, Value}- 为此状态转换设置transition_option()postpone/0。当从Module:init/1返回或传递给enter_loop/4,5,6时,此动作将被忽略,因为在这些情况下没有要推迟的事件。postpone等同于{postpone, true}。{next_event, EventType, EventContent}- 此动作不设置任何transition_option(),而是存储指定的EventType和EventContent,以便在所有动作执行后插入。存储的事件会作为下一个要处理的事件插入队列中,排在任何已排队的事件之前。这些存储的事件的顺序会保留,因此包含列表中的第一个
next_event将成为第一个要处理的事件。当您想要可靠地区分以这种方式插入的事件与任何外部事件时,应使用
internal类型的事件。{change_callback_module, NewModule}- 将回调模块更改为NewModule,该模块将在调用所有后续状态回调时使用。
自 OTP 22.3 起。gen_statem引擎将通过在下一个状态回调之前调用NewModule:callback_mode/0来找出NewModule的回调模式。更改回调模块不会以任何方式影响状态转换,它只会更改处理事件的模块。请注意,
NewModule中的所有相关回调函数,如状态回调、NewModule:code_change/4、NewModule:format_status/1和NewModule:terminate/3必须能够处理旧模块的状态和数据。{push_callback_module, NewModule}- 将当前回调模块推送到回调模块的内部堆栈的顶部,并将回调模块更改为NewModule。其他方面类似于上面的{change_callback_module, NewModule}。
自 OTP 22.3 起。pop_callback_module- 从回调模块的内部堆栈中弹出顶部模块,并将回调模块更改为弹出的模块。如果堆栈为空,则服务器会失败。其他方面类似于上面的{change_callback_module, NewModule}。
自 OTP 22.3 起。
-type callback_mode() :: state_functions | handle_event_function.
每个状态一个函数或一个通用的事件处理程序。
回调模式 通过 Module:callback_mode/0 的返回值选择。
state_functions- 状态必须是state_name/0类型,并且每个状态使用一个回调函数,即Module:StateName/3。handle_event_function- 状态可以是任何项,并且回调函数Module:handle_event/4用于所有状态。
当启动 gen_statem 时,在代码更改之后,以及在使用任何动作 change_callback_module、push_callback_module 或 pop_callback_module 更改回调模块之后,会调用函数 Module:callback_mode/0。结果会缓存起来,供后续调用 状态回调 时使用。
-type callback_mode_result() :: callback_mode() | [callback_mode() | state_enter()].
来自 Module:callback_mode/0 的返回值。
这是从 Module:callback_mode/0 返回的类型,用于选择 回调模式,以及是否执行 状态进入调用。
-type data() :: term().
服务器的通用状态数据。
状态机实现要在其中存储其需要的任何服务器数据的项。这与 state/0 本身的区别在于,此数据中的更改不会导致推迟的事件重试。因此,如果此数据中的更改会更改处理的事件集,则该数据项应改为 state/0 的一部分。
-type enter_action() :: hibernate | {hibernate, Hibernate :: hibernate()} | timeout_action() | reply_action().
任何回调的操作:休眠、超时或回复。
当允许 action/0 时,以及从状态进入调用时,允许使用这些转换动作,并且可以通过从状态回调返回它们来调用,从 Module:init/1 返回,或者通过将它们传递给 enter_loop/4,5,6 来调用。
动作按照包含列表的顺序执行。
设置转换选项的动作会覆盖任何先前相同类型的动作,因此包含列表中的最后一个动作胜出。例如,最后一个event_timeout/0会覆盖列表中任何先前的event_timeout/0。
{hibernate, Value}- 为此状态转换设置transition_option/0hibernate/0。hibernate等同于{hibernate, true}。
-type enter_loop_opt() :: {hibernate_after, HibernateAfterTimeout :: timeout()} | {debug, Dbgs :: [sys:debug_option()]}.
用于 enter_loop/4,5,6、start/3,4、start_link/3,4 和 start_monitor/3,4 函数的服务器启动选项。
请参阅start_link/4。
-type event_content() :: term().
从事件的来源发送到状态回调的事件有效负载。
请参阅 event_type,其中描述了不同事件类型的来源,以及事件的内容来源。
-type event_handler_result(StateType) :: event_handler_result(StateType, term()).
-type event_handler_result(StateType, DataType) :: {next_state, NextState :: StateType, NewData :: DataType} | {next_state, NextState :: StateType, NewData :: DataType, Actions :: [action()] | action()} | state_callback_result(action(), DataType).
处理事件后来自状态回调的返回值。
如果回调模式是 state_functions,则 StateType 为 state_name/0,如果 回调模式 是 handle_event_function,则 StateType 为 state/0。
{next_state, NextState, NewData [, Actions]}-gen_statem执行到NextState的状态转换(可能与当前状态相同),将NewData设置为当前服务器data/0,并执行所有Actions。如果NextState =/= CurrentState,则状态转换是状态更改。
等待事件的时间。
启动由timeout_action/0 Time 设置的计时器,或 {timeout, Time, EventContent [, Options]}。
当计时器到期时,将生成 event_type/0 timeout 类型的事件。有关如何解释 Time 和 Options 的信息,请参阅 erlang:start_timer/4。将来不一定会支持 erlang:start_timer/4 Options。
任何到达的事件都会取消此超时。请注意,重试或插入的事件算作已到达。如果状态超时零事件在此超时请求之前生成,则该事件也算作已到达。
如果 Time 是 infinity,则不会启动计时器,因为它永远不会过期。
如果 Time 是相对的,并且为 0,则实际上不会启动计时器,而是将超时事件排入队列,以确保它在任何尚未收到的外部事件之前处理,但在已排队的事件之后处理。
请注意,不可能也不需要取消此超时,因为它会被任何其他事件自动取消,这意味着每当调用可能想要取消此超时的回调时,计时器已经取消或过期。
可以使用 {timeout, update, NewEventContent} 操作更新计时器 EventContent,而不会影响过期时间。
-type event_type() :: external_event_type() | timeout_event_type() | internal.
internal 事件只能由状态机本身通过转换动作 next_event 生成。
-type external_event_type() :: {call, From :: from()} | cast | info.
来自 call、cast 或常规进程消息的事件;“info”。
类型 {call, From} 源自 API 函数 call/2,3 或 send_request/2。该事件包含 From,它指示使用 reply_action/0 或 reply/2,3 调用回复的对象。
类型 cast 源自 API 函数 cast/2。
类型 info 源自发送给 gen_statem 进程的常规进程消息。
-type format_status() :: #{state => state(), data => data(), reason => term(), queue => [{event_type(), event_content()}], postponed => [{event_type(), event_content()}], timeouts => [{timeout_event_type(), event_content()}], log => [sys:system_event()]}.
描述服务器状态的映射。
键值包括
state- 当前状态。data- 状态数据。reason- 导致进程终止的原因。queue- 事件队列。postponed- 延迟事件的队列。timeouts- 活动的超时。log- 服务器的 sys 日志。
新的关联可能会在不事先通知的情况下添加到状态映射中。
call 事件的回复目标。
通过例如 {reply, From, Reply} 动作回复使用 call/2,3 调用 gen_statem 服务器的进程时使用的目标地址。
等待命名超时事件的时间。
启动由 timeout_action/0 {{timeout, Name}, Time, EventContent [, Options]} 设置的计时器。
当计时器到期时,会生成一个类型为 event_type/0 {timeout, Name} 的事件。有关 Time 和 Options 如何解释,请参阅 erlang:start_timer/4。未来的 erlang:start_timer/4 Options 不一定支持。
如果 Time 是 infinity,则不会启动计时器,因为它永远不会过期。
如果 Time 是相对的并且为 0,则实际上不会启动计时器,而是将超时事件放入队列,以确保在处理任何尚未接收到的外部事件之前对其进行处理。
当计时器正在运行时,使用相同的 Name 设置计时器将使用新的超时值重新启动它。因此,可以通过将其设置为 infinity 来取消特定的超时。也可以使用 {{timeout, Name}, cancel} 动作更明确地取消它。
可以使用 {{timeout, Name}, update, NewEventContent} 动作更新计时器 EventContent,而不会影响过期时间。
-type hibernate() :: boolean().
使服务器进程休眠。
如果为 true,则通过调用 proc_lib:hibernate/3 使 gen_statem 进入休眠状态,然后再进入 receive 以等待新的外部事件。
还有一个服务器启动选项 {hibernate_after, Timeout} 用于自动休眠。
注意
如果在请求休眠时有排队的事件要处理,则会进行优化,不会休眠,而是调用
erlang:garbage_collect/0,以更有效的方式模拟gen_statem进入休眠状态并立即被排队的事件唤醒。
-type init_result(StateType) :: init_result(StateType, term()).
-type init_result(StateType, DataType) :: {ok, State :: StateType, Data :: DataType} | {ok, State :: StateType, Data :: DataType, Actions :: [action()] | action()} | ignore | {stop, Reason :: term()} | {error, Reason :: term()}.
来自 Module:init/1 的返回值。
对于成功的初始化,State 是初始的 state/0,Data 是 gen_statem 的初始服务器 data/0。
当进入第一个 state 时,将执行 Actions,就像 状态回调 一样,只是强制将 postpone 动作设为 false,因为没有事件要延迟。
对于不成功的初始化,应使用 {stop, Reason}、{error, Reason} 或 ignore;请参阅 start_link/3,4。
自 OTP 26.0 起允许使用 {error, Reason}。
自 OTP 19.1 起存在 {ok, ...} 元组,在此之前它们没有 ok 标签。这是在 OTP 20.0 中 gen_statem 替换 gen_fsm 之前。
-type postpone() :: boolean().
推迟事件以便稍后处理。
如果为 true,则延迟当前事件。在状态更改(NextState =/= State)后,将重试该事件。
回复 call/2,3。
可以通过从 状态回调、从 Module:init/1 返回,或将其传递给 enter_loop/4,5,6 来调用此转换动作。
它不设置任何 transition_option(),而是回复 call/3 中等待回复的调用者。From 必须是调用 状态回调 时 {call, From} 参数中的项。
请注意,由于此服务器中之前没有调用 状态回调,因此从 Module:init/1 或 enter_loop/4,5,6 中使用此操作会很奇怪,甚至有点像魔法。
-opaque reply_tag()
将回复与相应请求关联的句柄。
-opaque request_id()
不透明的请求标识符。有关详细信息,请参阅 send_request/2。
-opaque request_id_collection()
不透明的请求标识符 (request_id/0) 集合。
每个请求标识符都可以与用户选择的标签关联。有关详细信息,请参阅 reqids_new/0。
异步调用的响应超时。
用于设置等待使用 receive_response/2、receive_response/3、wait_response/2 或 wait_response/3 响应的最长时间限制。使用的时间单位是 毫秒。
当前有效的值
0..4294967295- 相对于当前时间的超时,单位为毫秒。infinity- 无限超时。也就是说,操作永远不会超时。{abs, Timeout}- 绝对的 Erlang 单调时间超时,单位为毫秒。也就是说,当erlang:monotonic_time(millisecond)返回的值大于或等于Timeout时,操作将超时。Timeout不得指定超过4294967295毫秒的未来时间。当你有一个对应于整个请求集合的响应截止时间 (request_id_collection/0) 时,使用绝对值指定超时特别方便,因为你不必反复重新计算截止时间之前的相对时间。
-type server_name() :: {local, atom()} | {global, GlobalName :: term()} | {via, RegMod :: module(), Name :: term()}.
服务器名称规范:local、global 或 via 注册。
启动 gen_statem 服务器时使用的名称规范。请参阅下面的 start_link/3 和 server_ref/0。
-type server_ref() :: pid() | (LocalName :: atom()) | {Name :: atom(), Node :: atom()} | {global, GlobalName :: term()} | {via, RegMod :: module(), ViaName :: term()}.
服务器规范:pid/0 或已注册的 server_name/0。
在 call/2,3 中使用以指定服务器。
它可以是
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,这些函数的行为应与global中的相应函数类似。因此,{via, global, GlobalName}与{global, GlobalName}相同。
来自 start_monitor/3,4 函数的返回值。
与 start_link/4 相同,但成功返回时会将进程 ID 和 监视引用 包装在 {ok, {pid(),reference()}} 元组中。
-type start_opt() :: {timeout, Time :: timeout()} | {spawn_opt, [proc_lib:start_spawn_option()]} | enter_loop_opt().
用于 start/3,4、start_link/3,4 和 start_monitor/3,4 函数的服务器启动选项。
请参阅start_link/4。
来自 start/3,4 和 start_link/3,4 函数的 返回值。
请参见 start_link/4。
-type state() :: state_name() | term().
状态名称或状态术语。
如果 回调模式 为 handle_event_function,则状态可以是任意项。在状态更改 (NextState =/= State) 后,所有延迟的事件都会重试。
假设比较两个状态是否严格相等是一个快速操作,因为对于每个状态转换,gen_statem 引擎必须推断它是否是状态更改。
注意
通常,状态项越小,比较速度越快。
请注意,如果为状态转换返回“相同”的状态项(或者使用了没有
NextState字段的返回操作),则相等性比较始终是快速的,因为可以从项句柄中看到这一点。但是,如果返回新构造的状态项,则必须遍历旧状态项和新状态项,直到找到不相等的地方,或者直到两个项都完全遍历完毕。
因此,可以使用比较速度快的大型状态项,但很容易意外出错。使用小型状态项是安全的选择。
-type state_callback_result(ActionType, DataType) :: {keep_state, NewData :: DataType} | {keep_state, NewData :: DataType, Actions :: [ActionType] | ActionType} | keep_state_and_data | {keep_state_and_data, Actions :: [ActionType] | ActionType} | {repeat_state, NewData :: DataType} | {repeat_state, NewData :: DataType, Actions :: [ActionType] | ActionType} | repeat_state_and_data | {repeat_state_and_data, Actions :: [ActionType] | ActionType} | stop | {stop, Reason :: term()} | {stop, Reason :: term(), NewData :: DataType} | {stop_and_reply, Reason :: term(), Replies :: [reply_action()] | reply_action()} | {stop_and_reply, Reason :: term(), Replies :: [reply_action()] | reply_action(), NewData :: DataType}.
来自任何 状态回调 的返回值。
ActionType 如果状态回调是通过 状态进入调用 调用的,则为 enter_action/0,如果状态回调是通过事件调用的,则为 action/0。
{keep_state, NewData [, Actions]}- 与{next_state, CurrentState, NewData [, Actions]}相同。keep_state_and_data | {keep_state_and_data, Actions}- 与{keep_state, CurrentData [, Actions]}相同。{repeat_state, NewData [, Actions]}- 如果gen_statem运行带有 状态进入调用,则重复状态进入调用,请参见类型transition_option/0。除此之外,{repeat_state, NewData [, Actions]}与{keep_state, NewData [, Actions]}相同。repeat_state_and_data | {repeat_state_and_data, Actions}- 与{repeat_state, CurrentData [, Actions]}相同。{stop, Reason [, NewData]}- 通过调用Module:terminate/3并指定Reason和NewData(如果指定),终止gen_statem。将此原因的退出信号发送到链接的进程和端口。stop- 与{stop, normal}相同。{stop_and_reply, Reason, Replies [, NewData]}- 发送所有Replies,然后像使用{stop, Reason [, NewData]}一样终止gen_statem。
所有这些项都是元组或原子,并且在 gen_statem 的所有未来版本中都将如此。
-type state_enter() :: state_enter.
状态进入调用的回调模式 修饰符:原子 state_enter。
两种回调模式都可以使用状态进入调用,这通过将 state_enter 标志添加到 回调模式 从 Module:callback_mode/0 返回的值中来选择。
如果 Module:callback_mode/0 返回包含 state_enter 的列表,则 gen_statem 引擎将在每次状态更改时,即 NextState =/= CurrentState,使用参数 (enter, OldState, Data) 或 (enter, OldState, State, Data) 调用 状态回调,具体取决于 回调模式。
这可能看起来像一个事件,但实际上是在前一个 状态回调 返回后,并且在将任何事件传递到新的 状态回调 之前执行的调用。请参见 Module:StateName/3 和 Module:handle_event/4。通过从状态回调返回 repeat_state 或 repeat_state_and_data 操作,可以重复执行状态进入调用而无需进行状态更改。
如果 Module:callback_mode/0 未返回包含 state_enter 的列表,则不会执行状态进入调用。
如果 Module:code_change/4 应该转换状态,则将其视为状态重命名而不是状态更改,这不会导致状态进入调用。
请注意,在进入初始状态之前将执行状态进入调用,这可以看作是从无状态到初始状态的状态更改。在这种情况下,OldState =:= State,这对于后续状态更改不会发生,但在重复状态进入调用时会发生。
-type state_enter_result(State) :: state_enter_result(State, term()).
-type state_enter_result(State, DataType) :: {next_state, State, NewData :: DataType} | {next_state, State, NewData :: DataType, Actions :: [enter_action()] | enter_action()} | state_callback_result(enter_action(), DataType).
在状态进入调用之后,来自状态回调的返回值。
State 是当前状态,不能更改,因为状态回调是通过 状态进入调用 调用的。
{next_state, State, NewData [, Actions]}-gen_statem执行到State的状态转换,该状态必须等于当前状态,设置NewData,并执行所有Actions。
-type state_name() :: atom().
在 回调模式 state_functions 中的状态名称。
如果 回调模式 为 state_functions,则状态必须是原子。在状态更改 (NextState =/= State) 后,所有延迟的事件都会重试。请注意,状态 terminate 不能使用,因为它会与可选回调函数 Module:terminate/3 冲突。
在当前状态下等待的时间。
启动由 timeout_action/0 或 {state_timeout, Time, EventContent [, Options]} 设置的定时器。
当定时器到期时,将生成 event_type/0 state_timeout 类型的事件。有关如何解释 Time 和 Options 的信息,请参见 erlang:start_timer/4。未来 erlang:start_timer/4 的 Options 不一定会受支持。
如果正在运行,则状态更改会取消此定时器。也就是说,如果启动此定时器的 timeout_action/0 是状态更改的 action/0 列表的一部分,NextState =/= CurrentState,则定时器在 NextState 中运行。
如果状态机保持在该新状态(现在是当前状态),则定时器将运行直到到期,从而创建超时事件。如果状态机从当前状态更改状态,则定时器将被取消。在从当前状态进行状态更改期间,可以为下一个 NextState 启动新的状态超时。
如果启动此定时器的 timeout_action/0 是状态转换(而不是状态更改)的 action/0 列表的一部分,则定时器在当前状态下运行。
如果 Time 是 infinity,则不会启动计时器,因为它永远不会过期。
如果 Time 是相对的且为 0,则实际上不会启动定时器,而是将超时事件排队,以确保它在任何尚未接收到的外部事件之前得到处理。
在定时器运行时设置此定时器将使用新的超时值重新启动它。因此,可以通过将其设置为 infinity 来取消此超时。也可以使用 {state_timeout, cancel} 更明确地取消它。
可以使用 {state_timeout, update, NewEventContent} 操作更新定时器 EventContent,而不会影响到期时间。
-type timeout_action() :: (Time :: event_timeout()) | {timeout, Time :: event_timeout(), EventContent :: event_content()} | {timeout, Time :: event_timeout(), EventContent :: event_content(), Options :: timeout_option() | [timeout_option()]} | {{timeout, Name :: term()}, Time :: generic_timeout(), EventContent :: event_content()} | {{timeout, Name :: term()}, Time :: generic_timeout(), EventContent :: event_content(), Options :: timeout_option() | [timeout_option()]} | {state_timeout, Time :: state_timeout(), EventContent :: event_content()} | {state_timeout, Time :: state_timeout(), EventContent :: event_content(), Options :: timeout_option() | [timeout_option()]} | timeout_cancel_action() | timeout_update_action().
事件超时、通用超时或状态超时。
可以通过从 状态回调、从 Module:init/1 返回这些转换操作,或将其传递给 enter_loop/4,5,6 来调用它们。
这些超时操作设置超时 转换选项。
Time- 是{timeout, Time, Time}的缩写,也就是说,超时消息是超时时间。存在此形式是为了允许 状态回调 返回值{next_state, NextState, NewData, Time},如gen_fsm中所示。{timeout, Time, EventContent [, Options]}- 将transition_option/0event_timeout/0设置为Time,其中包含EventContent和超时选项Options。{{timeout,Name}, Time, EventContent [, Options]}- 将transition_option/0generic_timeout/0设置为Time,用于超时Name,并包含EventContent和超时选项Options。
自 OTP 20.0 起.{state_timeout, Time, EventContent [, Options]}- 将transition_option/0state_timeout/0设置为Time,并包含EventContent和超时选项Options。
自 OTP 19.3 起.
-type timeout_cancel_action() :: {timeout, cancel} | {{timeout, Name :: term()}, cancel} | {state_timeout, cancel}.
比将其设置为“无限”更清晰地取消超时的方法。
一直以来,都可以使用 timeout_action/0 和 Time = infinity 来取消超时。因为设置新的超时时间会覆盖正在运行的计时器,并且将时间设置为 infinity 会优化为不设置计时器(它永远不会过期)。使用此操作可以更清晰地显示意图。
-type timeout_event_type() :: timeout | {timeout, Name :: term()} | state_timeout.
状态机可以使用相应的 timeout_action/0 为自身生成的超时事件类型
| 超时类型 | 操作 | 事件类型 |
|---|---|---|
| 事件超时 | {timeout, Time, ...} | timeout |
| 通用超时 | {{timeout, Name}, Time, ...} | {timeout, Name} |
| 状态超时 | {state_timeout, Time, ...} | state_timeout |
简而言之,使用 EventType 设置超时的操作是 {EventType, Time, ...}。
-type timeout_option() :: {abs, Abs :: boolean()}.
超时计时器启动选项,用于选择过期绝对时间。
如果 Abs 为 true,则启动绝对计时器;如果为 false,则启动相对计时器,这是默认设置。有关详细信息,请参阅 erlang:start_timer/4。
-type timeout_update_action() :: {timeout, update, EventContent :: event_content()} | {{timeout, Name :: term()}, update, EventContent :: event_content()} | {state_timeout, update, EventContent :: event_content()}.
更新 EventContent,而不影响过期时间。
为正在运行的超时计时器设置新的 EventContent。有关如何启动超时,请参阅 timeout_action()。
如果此类型的超时未激活,则会像以相对 Time = 0 启动超时时一样插入超时事件。这是一个立即过期的超时自动启动,因此如果例如通用超时名称拼写错误,则会出现噪声。
-type transition_option() :: postpone() | hibernate() | event_timeout() | generic_timeout() | state_timeout().
由 操作设置的状态转换选项。
这些决定了在状态转换期间发生的事情。当 状态回调 处理完事件并返回时,就会发生状态转换。以下是状态转换的步骤顺序
所有返回的 actions 都按照出现的顺序处理。在此步骤中,将发送由任何
reply_action/0生成的所有回复。其他操作设置transition_option/0,这些选项将在后续步骤中生效。如果使用了 状态进入调用,则无论是初始状态还是回调结果
repeat_state或repeat_state_and_data中的一个,gen_statem引擎都会使用参数(enter, State, Data)或(enter, State, State, Data)(取决于 回调模式)调用当前状态回调,并在返回时再次从该序列的顶部开始。如果使用了 状态进入调用,并且状态发生变化,则
gen_statem引擎会使用参数(enter, OldState, Data)或(enter, OldState, State, Data)(取决于 回调模式)调用新的状态回调,并在返回时再次从该序列的顶部开始。如果
postpone/0为true,则当前事件将被延迟。如果这是状态更改,则传入事件的队列将被重置为从最早的延迟事件开始。
所有使用
action/0next_event存储的事件都将被插入到先前排队的事件之前处理。将处理超时计时器
event_timeout/0、generic_timeout/0和state_timeout/0。保证零时间的超时将在任何外部尚未接收到的事件之前传递到状态机。因此,如果请求了这样的超时,则会将相应的超时零事件作为最新接收的事件排队;也就是说,在已经排队的事件(如插入和延迟的事件)之后。任何事件都会取消
event_timeout/0,因此只有当事件队列为空时,才会生成零时间的事件超时。状态更改 会取消
state_timeout/0,并且此类型的任何新转换选项都属于新状态,即state_timeout/0应用于状态机进入的状态。如果存在排队的事件,则使用最早排队的事件调用可能新状态的 状态回调,并且我们再次从该序列的顶部开始。
否则,
gen_statem将进入receive或休眠状态(如果hibernate/0为true),以等待下一条消息。在休眠状态下,下一个非系统事件会唤醒gen_statem,或者说,下一条传入消息会唤醒gen_statem,但如果是系统事件,它会立即返回休眠状态。当新消息到达时,将使用相应的事件调用 状态回调,并且我们再次从该序列的顶部开始。
注意
零超时(时间为
0的超时)的行为与 Erlang 的receive ... after 0 ... end略有不同。后者会在有消息时接收一条消息,而使用
timeout_action/0{timeout, 0}则不会接收任何外部事件。
gen_server的超时工作方式类似于 Erlang 的receive ... after 0 ... end,与gen_statem相反。
回调
-callback callback_mode() -> callback_mode_result().
选择回调模式,并可能选择 状态进入调用。
当 gen_statem 需要找出回调模块的 回调模式 时,会调用此函数。
该值由 gen_statem 缓存以提高效率,因此此函数仅在服务器启动后、代码更改后以及更改回调模块后调用一次,但在调用当前回调模块代码中的第一个 状态回调 之前调用。未来版本的 gen_statem 可能会添加更多场合。
服务器启动发生在 Module:init/1 返回时或调用 enter_loop/4,5,6 时。代码更改发生在 Module:code_change/4 返回时。当 状态回调 返回任何操作 change_callback_module、push_callback_module 或 pop_callback_module 时,会发生回调模块的更改。
CallbackMode 要么只是 callback_mode/0,要么是包含 callback_mode/0 以及可能的原子 state_enter 的列表。
注意
如果此函数的主体未返回内联常量值,则回调模块正在执行某些奇怪的操作。
-callback code_change(OldVsn :: term() | {down, term()}, OldState :: state(), OldData :: data(), Extra :: term()) -> {ok, NewState :: state(), NewData :: data()} | (Reason :: term()).
当 gen_statem 在版本升级/降级期间更新其内部状态时,会调用此函数。也就是说,当 appup 文件中指定了指令 {update, Module, Change, ...}(其中 Change = {advanced, Extra})时。有关更多信息,请参阅 OTP 设计原则。
对于升级,OldVsn 是 Vsn,对于降级,OldVsn 是 {down, Vsn}。Vsn 由回调模块 Module 的旧版本的 vsn 属性定义。如果未定义此类属性,则版本为 Beam 文件的校验和。
OldState 和 OldData 是 gen_statem 的内部状态。
Extra 从更新指令的 {advanced, Extra} 部分“按原样”传递。
如果成功,该函数必须在 {ok, NewState, NewData} 元组中返回更新后的内部状态。
如果函数返回失败的 Reason,则正在进行的升级将失败并回滚到旧版本。请注意,Reason 不能是 {ok, _, _} 元组,因为它将被视为 {ok, NewState, NewData} 元组,并且匹配 {ok, _} 的元组也是无效的失败 Reason。建议使用原子作为 Reason,因为它将被包装在 {error, Reason} 元组中。
还要注意,在升级 gen_statem 时,此函数以及 appup 文件中的 Change = {advanced, Extra} 参数不仅需要更新内部状态或处理 Extra 参数。如果升级或降级应更改回调模式,也需要此参数,否则代码更改后的回调模式将不会生效,很可能会导致服务器崩溃。
如果服务器使用任何操作 change_callback_module、push_callback_module 或 pop_callback_module 更改回调模块,请注意,始终是当前回调模块将接收到此回调调用。当前回调模块处理当前状态和数据更新应该不足为奇,但它必须能够以某种方式处理它不熟悉的状态和数据部分。
在 supervisor 子规范中,有一个模块列表,建议只包含回调模块。对于具有多个回调模块的 gen_statem,没有必要列出所有模块,甚至可能无法列出,因为该列表可能会在代码升级后更改。如果此列表仅包含启动回调模块(如建议的那样),那么重要的是在进行同步代码替换时升级该模块。然后,发布处理程序会得出结论,升级该模块的升级需要暂停、代码更改并恢复任何在其子规范中声明正在使用该模块的服务器。再次强调;当前回调模块将接收到 Module:code_change/4 调用。
注意
如果在
.appup文件中指定了Change = {advanced, Extra}的情况下进行发布升级/降级,而未实现Module:code_change/4,则进程将以退出原因undef崩溃。
-callback format_status(Status) -> NewStatus when Status :: format_status(), NewStatus :: format_status().
格式化/限制状态值。
此函数由 gen_statem 进程调用,以便格式化/限制服务器状态,用于调试和日志记录目的。
在以下情况下调用此函数:
- 调用
sys:get_status/1,2以获取gen_statem状态。 gen_statem进程异常终止并记录错误。
此函数对于更改这些情况下 gen_statem 状态的形式和外观非常有用。希望更改 sys:get_status/1,2 返回值以及其状态如何显示在终止错误日志中的回调模块,会导出一个 Module:format_status/1 实例,该实例将获取一个描述 gen_statem 当前状态的映射 Status,并且应返回一个包含与输入映射相同键的映射 NewStatus,但它可以转换某些值。
此函数的一个用例是返回紧凑的替代状态表示形式,以避免在日志文件中打印大型状态项。另一个用例是从错误日志中隐藏敏感数据。
示例
format_status(Status) ->
maps:map(
fun(state,State) ->
maps:remove(private_key, State);
(message,{password, _Pass}) ->
{password, removed};
(_,Value) ->
Value
end, Status).注意
此回调是可选的,因此回调模块不需要导出它。
gen_statem模块提供了此函数的默认实现,该实现返回{State, Data}。如果导出了此回调但失败,为了隐藏可能的敏感数据,默认函数将改为返回
{State, Info},其中Info只说明Module:format_status/2已崩溃。
-callback format_status(StatusOption, [[{Key :: term(), Value :: term()}] | state() | data()]) -> Status :: term() when StatusOption :: normal | terminate.
格式化/限制状态值。
此函数由 gen_statem 进程调用,以便格式化/限制服务器状态,用于调试和日志记录目的。
在以下情况下调用此函数:
调用
sys:get_status/1,2之一以获取gen_statem状态。在这种情况下,Opt设置为原子normal。gen_statem异常终止并记录错误。在这种情况下,Opt设置为原子terminate。
此函数对于更改这些情况下 gen_statem 状态的形式和外观非常有用。希望更改 sys:get_status/1,2 返回值以及其状态如何显示在终止错误日志中的回调模块,应导出一个 Module: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 返回值的其余部分保持一致。
此函数的一个用途是返回紧凑的替代状态表示形式,以避免在日志文件中打印大型状态项。另一个用途是从错误日志中隐藏敏感数据。
注意
此回调是可选的,因此回调模块不需要导出它。
gen_statem模块提供了此函数的默认实现,该实现返回{State, Data}。如果导出了此回调但失败,为了隐藏可能的敏感数据,默认函数将改为返回
{State, Info},其中Info只说明Module:format_status/2已崩溃。
-callback handle_event(enter, OldState, CurrentState, Data) -> state_enter_result(CurrentState) when OldState :: state(), CurrentState :: state(), Data :: data(); (EventType, EventContent, CurrentState, Data) -> event_handler_result(state()) when EventType :: event_type(), EventContent :: event_content(), CurrentState :: state(), Data :: data().
回调模式 handle_event_function 中的状态回调。
每当 gen_statem 从 call/2,3、cast/2 或作为正常进程消息接收到事件时,都会调用此函数。
如果 EventType 为 {call, From},则调用者等待回复。回复可以从此回调或任何其他状态回调发送,方法是在 Actions、Replies 中返回 {reply, From, Reply},或者通过调用 reply(From, Reply)。
如果此函数返回的下一个状态与当前状态不匹配(=/=),则所有推迟的事件都会在下一个状态中重试。
有关 gen_statem 从此函数返回后可以设置的选项和可以执行的操作,请参见 action/0。
当 gen_statem 使用 状态进入调用运行时,此函数在每次状态更改期间也会使用参数 (enter, OldState, ...) 调用。在这种情况下,对可能返回的 操作有一些限制
不允许使用
postpone/0,因为状态进入调用不是事件,因此没有事件可以推迟。不允许使用
{next_event, _, _},因为使用状态进入调用不应影响事件的消费和产生方式。不允许从此调用更改状态。如果您返回
{next_state, NextState, ...},并且NextState =/= State,则gen_statem会崩溃。请注意,实际上允许使用
{repeat_state, NewData, ...},尽管这没什么意义,因为您会立即使用新的状态进入调用再次被调用,这只是一种奇怪的循环方式,并且在 Erlang 中有更好的循环方式。如果你不更新
NewData,并且有一些循环终止条件,或者如果你使用{repeat_state_and_data, _}或repeat_state_and_data,你将会陷入无限循环!建议你使用
{keep_state, ...}、{keep_state_and_data, _}或keep_state_and_data,因为无论如何都无法从状态进入调用更改状态。
请注意,你可以使用 throw 来返回结果,这会很有用。例如,从无法返回 {next_state, State, Data} 的复杂代码深处使用 throw(keep_state_and_data) 来退出,因为 State 或 Data 不再在作用域内。
-callback init(Args :: term()) -> init_result(state()).
初始化状态机。
每当使用 start_link/3,4、start_monitor/3,4 或 start/3,4 启动 gen_statem 时,新进程都会调用此函数来初始化实现状态和服务器数据。
Args 是提供给启动函数的 Args 参数。
注意
请注意,如果
gen_statem是通过proc_lib和enter_loop/4,5,6启动的,则不会调用此回调。由于此回调不是可选的,因此在这种情况下可以将其实现为-spec init(_) -> no_return(). init(Args) -> erlang:error(not_implemented, [Args]).
-callback 'StateName'(enter, OldStateName :: state_name(), data()) -> state_enter_result(state_name); (EventType :: event_type(), EventContent :: event_content(), Data :: data()) -> event_handler_result(state_name()).
在 回调模式 state_functions 中的状态回调。
状态回调,用于处理状态 StateName 中的所有事件,其中 StateName :: state_name() 必须是 atom/0。
StateName 不能是 terminate,因为这会与回调函数 Module:terminate/3 冲突。
除此之外,在进行状态更改时,下一个状态必须始终是 atom/0,此函数等效于 Module:handle_event(EventType, EventContent, ?FUNCTION_NAME, Data),它是 回调模式 handle_event_function 中的状态回调。
-callback terminate(Reason :: normal | shutdown | {shutdown, term()} | term(), CurrentState :: state(), data()) -> any().
处理状态机终止。
当 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 是因为错误而终止,并且会使用 logger 发出错误报告。
当 gen_statem 进程退出时,会向链接的进程和端口发送带有相同原因的退出信号,就像任何进程一样。
函数
-spec call(ServerRef :: server_ref(), Request :: term()) -> Reply :: term().
-spec call(ServerRef :: server_ref(), Request :: term(), Timeout :: timeout() | {clean_timeout, T :: timeout()} | {dirty_timeout, T :: timeout()}) -> Reply :: term().
调用服务器:发送请求并等待响应。
通过发送请求并等待直到收到响应,对 gen_statem ServerRef 进行同步调用。
gen_statem 使用 event_type/0 {call, From} 和事件内容 Request 调用状态回调。
服务器的回复从状态回调发送,方法是返回 转换操作 {reply, From, Reply},使用 Replies 列表中的此类回复操作调用 reply(Replies),或调用 reply(From, Reply)。
Timeout 是一个大于 0 的整数,它指定等待回复的毫秒数,或者是原子 infinity,表示无限期等待,这是默认值。如果在指定时间内未收到回复,则函数调用失败。
现在通过使用进程别名避免了之前在出现网络问题或使用 dirty_timeout 时可能出现的延迟回复问题。{clean_timeout, T} 和 {dirty_timeout, T} 因此不再有任何用途,并且将与 Timeout 的效果相同,同时它们也同样高效。
此调用也可能失败,例如,如果 gen_statem 在此函数调用之前或期间死亡。
当此调用失败时,它会 退出 调用进程。退出项的形式为 {Reason, Location},其中 Location = {gen_statem, call, ArgList}。请参阅 gen_server:call/3,其中描述了退出项中 Reason 的相关值。
-spec cast(ServerRef :: server_ref(), Msg :: term()) -> ok.
向服务器发送一个事件。
向 gen_statem ServerRef 发送异步 cast 事件,并立即返回 ok,忽略目标节点或 gen_statem 是否存在。
gen_statem 使用 event_type/0 cast 和事件内容 Msg 调用状态回调。
-spec check_response(Msg, ReqId) -> Result when Msg :: term(), ReqId :: request_id(), Response :: {reply, Reply :: term()} | {error, {Reason :: term(), server_ref()}}, Result :: Response | no_reply.
检查接收到的消息是否为请求响应。
检查 Msg 是否是与请求标识符 ReqId 对应的响应。该请求必须由 send_request/2 以及调用此函数的同一进程发出。
如果 Msg 是对句柄 ReqId 的回复,则请求的结果将在 Reply 中返回。否则,此函数将返回 no_reply 并且不进行任何清理,因此应重复调用该函数直到返回响应为止。
请参阅 call/3,了解如何处理请求以及 Reply 如何由 gen_statem 服务器发送。
如果调用此函数时 gen_statem 服务器进程已死,即 Msg 报告服务器死亡,则此函数将返回一个带有退出 Reason 的 error 返回值。
-spec check_response(Msg, ReqIdCollection, Delete) -> Result when Msg :: term(), ReqIdCollection :: request_id_collection(), Delete :: boolean(), Response :: {reply, Reply :: term()} | {error, {Reason :: term(), server_ref()}}, Result :: {Response, Label :: term(), NewReqIdCollection :: request_id_collection()} | no_request | no_reply.
检查接收到的消息是否为集合中的请求响应。
检查 Msg 是否是与 ReqIdCollection 中存储的请求标识符对应的响应。ReqIdCollection 的所有请求标识符必须对应于由调用此函数的进程使用 send_request/2 或 send_request/4 发出的请求。
响应中的 Label 等于与响应对应的请求标识符关联的 Label。请求标识符的 Label 在 将请求 ID 存储在集合中时,或者在使用 send_request/4 发送请求时关联。
与 check_response/2 相比,与特定请求标识符关联的返回结果或异常将包装在 3 元组 {Response, Label, NewReqIdCollection} 中。Response 是由 check_response/2 生成的值,Label 是与特定请求标识符关联的值,而 NewReqIdCollection 是一个可能已修改的请求标识符集合。
如果 ReqIdCollection 为空,则返回 no_request。
如果 Msg 与 ReqIdCollection 中的任何请求标识符都不对应,则返回 no_reply。
如果 Delete 等于 true,则与 Label 的关联已从生成的 NewReqIdCollection 中的 ReqIdCollection 中删除。如果 Delete 为 false,则 NewReqIdCollection 将等于 ReqIdCollection。请注意,删除关联并非免费的,并且包含已处理请求的集合仍然可以被后续调用 wait_response/3、check_response/3 和 receive_response/3 所使用。
但是,如果不删除已处理的关联,则上述调用将无法检测何时没有更多待处理的请求需要处理,因此你必须通过其他方式跟踪此情况,而不是依赖于 no_request 返回值。请注意,如果你将仅包含已处理或已放弃请求的关联的集合传递给此函数,则它始终会返回 no_reply。
-spec enter_loop(Module :: term(), Opts :: term(), State :: term(), Data :: term(), Actions) -> no_return() when Actions :: list(); (Module :: term(), Opts :: term(), State :: term(), Data :: term(), Server) -> no_return() when Server :: server_name() | pid().
使调用进程成为 gen_statem 服务器。
使用参数 Actions,等效于 enter_loop(Module, Opts, State, Data, self(), Actions)。
-spec enter_loop(Module :: module(), Opts :: [enter_loop_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:init/1 回调提供的更复杂的初始化过程时,此函数很有用。
Module 和 Opts 的含义与调用 start[link | monitor]/3,4 时相同。
如果 Server 为 self/0,则会创建一个匿名服务器,就像使用 start[link |_monitor]/3 时一样。如果 Server 为 server_name/0,则会创建一个命名服务器,就像使用 start[link |_monitor]/4 时一样。但是,server_name/0 名称必须在调用此函数之前已相应注册。
State、Data 和 Actions 的含义与 Module:init/1 的返回值相同。此外,回调模块不需要导出 Module:init/1 函数。
如果调用进程不是由 proc_lib 启动函数启动的,或者如果没有按照 server_name/0 注册,则该函数将失败。
-spec receive_response(ReqId) -> Result when ReqId :: request_id(), Response :: {reply, Reply :: term()} | {error, {Reason :: term(), server_ref()}}, Result :: Response | timeout.
-spec receive_response(ReqId, Timeout) -> Result when ReqId :: request_id(), Timeout :: response_timeout(), Response :: {reply, Reply :: term()} | {error, {Reason :: term(), server_ref()}}, Result :: Response | timeout.
接收请求响应。
接收与请求标识符 ReqId 相对应的响应。请求必须由 send_request/2 发送给 gen_statem 进程。此函数必须从发送 send_request/2 的同一进程调用。
Timeout 指定等待响应的时间。如果在指定时间内未收到响应,则此函数返回 timeout。假设服务器在支持别名的节点(在 OTP 24 中引入)上执行,则请求也将被放弃。也就是说,超时后将不会收到任何响应。否则,可能会在稍后收到一个无关的响应。
请参阅 call/3,了解如何处理请求以及 Reply 如何由 gen_statem 服务器发送。
如果 gen_statem 服务器进程在等待回复时死亡或终止,它将返回一个带有退出 Reason 的 error 返回值。
wait_response/2 和 receive_response/2 之间的区别在于,receive_response/2 在超时时放弃请求,以便忽略潜在的未来响应,而 wait_response/2 则不会。
-spec receive_response(ReqIdCollection, Timeout, Delete) -> Result when ReqIdCollection :: request_id_collection(), Timeout :: response_timeout(), Delete :: boolean(), Response :: {reply, Reply :: term()} | {error, {Reason :: term(), server_ref()}}, Result :: {Response, Label :: term(), NewReqIdCollection :: request_id_collection()} | no_request | timeout.
接收集合中的请求响应。
在 ReqIdCollection 中接收响应。ReqIdCollection 的所有请求标识符必须对应于使用 send_request/2 或 send_request/4 发出的请求,并且所有请求必须由调用此函数的进程发出。
响应中的 Label 是与响应对应的请求标识符关联的 Label。请求标识符的 Label 在 将请求 id 添加到集合时或使用 send_request/4 发送请求时关联。
与 receive_response/2 相比,与特定请求标识符关联的返回结果或异常将封装在 3 元组 {Response, Label, NewReqIdCollection} 中。Response 是 receive_response/2 将会产生的值,Label 是与特定 请求标识符 关联的值,NewReqIdCollection 是可能修改过的请求标识符集合。
如果 ReqIdCollection 为空,则将返回 no_request。
Timeout 指定等待响应的时间。如果在指定时间内未收到响应,则该函数返回 timeout。假设服务器在支持别名的节点(在 OTP 24 中引入)上执行,则 ReqIdCollection 标识的所有请求也将被放弃。也就是说,超时后将不会收到任何响应。否则,可能会在稍后收到无关的响应。
receive_response/3 和 wait_response/3 之间的区别在于,receive_response/3 在超时时放弃请求,以便忽略潜在的未来响应,而 wait_response/3 则不会。
如果 Delete 为 true,则与 Label 的关联将从结果 NewReqIdCollection 中的 ReqIdCollection 中删除。如果 Delete 为 false,则 NewReqIdCollection 将等于 ReqIdCollection。请注意,删除关联不是免费的,并且包含已处理请求的集合仍然可以通过后续调用 wait_response/3、check_response/3 和 receive_response/3 来使用。
但是,如果不删除已处理的关联,则上述调用将无法检测何时没有更多待处理的请求要处理,因此您必须通过其他方式来跟踪此情况,而不是依赖于 no_request 返回值。请注意,如果您将仅包含已处理或已放弃请求的关联的集合传递给此函数,它将始终阻塞直到 Timeout 过期,然后返回 timeout。
-spec reply(Replies :: [reply_action()] | reply_action()) -> ok.
发送一个或多个 call 回复。
当从状态回调返回 reply_action/0 不切实际或不可能时,gen_statem 回调可以使用此函数显式地向等待 call 请求的回复的进程发送一个或多个回复。
注意
使用此函数发送的回复在
sys调试输出中不可见。
将 call Reply 发送到 From。
当从 状态回调返回 reply_action/0 不切实际或不可能时,gen_statem 回调可以使用此函数显式地向等待 call 请求的回复的进程发送回复。
注意
使用此函数发送的回复在
sys调试输出中不可见。
-spec reqids_add(ReqId :: request_id(), Label :: term(), ReqIdCollection :: request_id_collection()) -> NewReqIdCollection :: request_id_collection().
将请求标识符存储在集合中。
存储 ReqId 并通过将此信息添加到 ReqIdCollection 并返回结果请求标识符集合,将 Label 与请求标识符关联。
-spec reqids_new() -> NewReqIdCollection :: request_id_collection().
创建一个空的请求标识符集合。
返回一个新的空请求标识符集合。请求标识符集合可用于处理多个未完成的请求。
使用 send_request/2 发出的请求的请求标识符可以使用 reqids_add/3 存储在集合中。此类请求标识符集合稍后可用于通过将集合作为参数传递给 receive_response/3、wait_response/3 或 check_response/3 来获取集合中与请求对应的响应。
reqids_size/1 可用于确定集合中请求标识符的数量。
-spec reqids_size(ReqIdCollection :: request_id_collection()) -> non_neg_integer().
返回 ReqIdCollection 中请求标识符的数量。
-spec reqids_to_list(ReqIdCollection :: request_id_collection()) -> [{ReqId :: request_id(), Label :: term()}].
将请求标识符集合转换为列表。
返回一个 {ReqId, Label} 元组列表,该列表对应于 ReqIdCollection 中所有带有相关标签的请求标识符。
-spec send_request(ServerRef :: server_ref(), Request :: term()) -> ReqId :: request_id().
发送异步 call 请求。
将 Request 发送到由 ServerRef 标识的 gen_statem 进程,并返回请求标识符 ReqId。
返回值 ReqId 稍后应与 receive_response/2、wait_response/2 或 check_response/2 一起使用,以获取请求的实际结果。除了将请求标识符直接传递给这些函数之外,它还可以使用 reqids_add/3 存储在请求标识符集合中。此类请求标识符集合稍后可用于通过将集合作为参数传递给 receive_response/3、wait_response/3 或 check_response/3 来获取集合中与请求对应的响应。如果您要将请求标识符存储在集合中,您可能需要考虑改用 send_request/4。
调用 gen_statem:wait_response(gen_statem:send_request(ServerRef, Request), Timeout) 可以看作等效于 gen_statem:call(Server, Request, Timeout),忽略错误处理。
请参阅 call/3,了解如何处理请求以及 Reply 如何由 gen_statem 服务器发送。
服务器的 Reply 由 receive_response/1,2、wait_response/1,2 或 check_response/2 函数之一返回。
-spec send_request(ServerRef :: server_ref(), Request :: term(), Label :: term(), ReqIdCollection :: request_id_collection()) -> NewReqIdCollection :: request_id_collection().
发送异步 call 请求并将其添加到请求标识符集合中。
将 Request 发送到由 ServerRef 标识的 gen_statem 进程。Label 将与操作的请求标识符关联,并添加到返回的请求标识符集合 NewReqIdCollection 中。稍后可以通过将此集合作为参数传递给 receive_response/3、wait_response/3 或 check_response/3 来获取集合中与请求对应的响应。
与调用 reqids_add(send_request(ServerRef, Request),Label, ReqIdCollection) 相同,但效率略高。
启动服务器,既不链接也不注册。
-spec start(ServerName :: server_name(), Module :: module(), Args :: term(), Opts :: [start_opt()]) -> start_ret().
启动服务器,已注册但未链接。
根据 OTP 设计原则(使用 proc_lib 原语)创建独立的 gen_statem 进程。由于它没有链接到调用进程,因此 supervisor 不能使用此启动函数来启动子进程。
有关参数和返回值的描述,请参见 start_link/4。
启动服务器,已链接但未注册。
等效于 start_link/4,但 gen_statem 进程未在任何 名称服务 中注册。
-spec start_link(ServerName :: server_name(), Module :: module(), Args :: term(), Opts :: [start_opt()]) -> start_ret().
启动服务器,已链接和注册。
根据 OTP 设计原则(使用 proc_lib 原语)创建一个 gen_statem 进程,该进程与调用进程链接生成。当 gen_statem 必须是监督树的一部分以便与其 supervisor 链接时,这是至关重要的。
生成的 gen_statem 进程调用 Module:init/1 来初始化服务器。为了确保同步的启动过程,start_link/3,4 在 Module:init/1 返回或失败之前不会返回。
ServerName 指定要为 gen_statem 进程注册的 server_name/0。如果 gen_statem 进程使用 start_link/3 启动,则不提供 ServerName 并且不注册 gen_statem 进程。
Module 是回调模块的名称。
Args 是任意项,作为参数传递给 Module:init/1。
Opts 中的启动选项
{timeout, Time}- 允许gen_statem进程在从Module:init/1返回之前花费Time毫秒,否则它将被终止,并且此启动函数返回{error, timeout}。{spawn_opt, SpawnOpts}-SpawnOpts作为选项列表传递给erlang:spawn_opt/2,用于生成gen_statem进程。请参阅proc_lib:start_spawn_option/0。注意
不允许使用生成选项
monitor,这会导致badarg失败。{hibernate_after, HibernateAfterTimeout}- 当gen_statem进程等待消息时,如果在HibernateAfterTimeout毫秒内未收到任何消息,则该进程会自动进入休眠状态(通过调用proc_lib:hibernate/3)。此选项也适用于enter_loop函数。请注意,还有一个
transition_option/0,用于从状态回调显式地使服务器休眠。{debug, Dbgs}- 通过sys激活调试。对于Dbgs中的每个条目,都会调用sys中的相应函数。此选项也适用于enter_loop函数。
返回值
ignore-Module:init/1返回了ignore。gen_statem进程以normal原因退出。{error, {already_started, OtherPid}}- 具有指定ServerName的进程已存在。OtherPid是该进程的pid/0。gen_statem进程在调用Module:init/1之前以normal原因退出。{error, timeout}-Module:init/1未在 启动超时 内返回。gen_statem进程已使用exit(_, kill)被杀死。Module:init/1返回{stop, Reason}或以原因Reason失败。gen_statem进程以原因Reason退出。- 或
Module:init/1返回{error, Reason}。gen_statem进程以normal原因正常退出。
如果返回值为 ignore 或 {error, _},则启动的 gen_statem 进程已终止。如果向调用进程传递了 'EXIT' 消息(由于进程链接),则该消息已被消耗。
警告
在 OTP 26.0 之前,如果启动的
gen_statem进程从Module:init/1返回例如{stop, Reason},则此函数可能会在启动的gen_statem进程终止之前返回{error, Reason},因此再次启动可能会失败,因为虚拟机资源(如注册名称)尚未注销,并且'EXIT'消息可能会稍后到达调用此函数的进程。但是,如果启动的
gen_statem进程在Module:init/1期间失败,则进程链接{'EXIT', Pid, Reason}消息会导致此函数返回{error, Reason},因此'EXIT'消息已被消耗,并且启动的gen_statem进程已终止。由于无法从
start_link/3,4的返回值中分辨这两种情况之间的差异,因此在 OTP 26.0 中清理了这种不一致性。
-spec start_monitor(Module :: module(), Args :: term(), Opts :: [start_opt()]) -> start_mon_ret().
启动服务器,已监视但既不链接也不注册。
等效于 start_monitor/4,但 gen_statem 进程未在任何 名称服务 中注册。
-spec start_monitor(ServerName :: server_name(), Module :: module(), Args :: term(), Opts :: [start_opt()]) -> start_mon_ret().
启动服务器,已监视和注册,但未链接。
根据 OTP 设计原则(使用 proc_lib 原语)创建一个独立的 gen_statem 进程,并原子地设置对新创建进程的监视器。
由于启动的进程未链接到调用进程,因此 supervisor 不能使用此启动函数来启动子进程。
有关参数和返回值的描述,请参见 start_link/4,但请注意,对于成功的启动,返回值有所不同,因为此函数返回 {ok, {Pid, Mon}},其中 Pid 是进程的进程标识符,而 Mon 是进程的监视器引用。如果启动不成功,则调用方将被阻塞,直到接收到 DOWN 消息并从调用方的消息队列中删除。
-spec stop(ServerRef :: server_ref()) -> ok.
-spec stop(ServerRef :: server_ref(), Reason :: term(), Timeout :: timeout()) -> ok.
停止服务器。
命令 gen_statem ServerRef 以指定的 Reason 退出,并等待其终止。 gen_statem 在退出前调用 Module:terminate/3。
如果服务器以预期的原因终止,则此函数返回 ok。任何 normal、shutdown 或 {shutdown, Term} 之外的原因都会导致通过 logger 发出错误报告。具有相同原因的退出信号将发送到链接的进程和端口。默认的 Reason 为 normal。
Timeout 是一个大于 0 的整数,指定等待服务器终止的毫秒数,或者使用原子 infinity 表示无限期等待。默认为 infinity。如果服务器未在指定时间内终止,调用将以原因 timeout 退出调用进程。
如果进程不存在,调用将以原因 noproc 退出调用进程;如果与运行服务器的远程 Node 的连接失败,则以原因 {nodedown, Node} 退出。
-spec wait_response(ReqId) -> Result when ReqId :: request_id(), Response :: {reply, Reply :: term()} | {error, {Reason :: term(), server_ref()}}, Result :: Response | timeout.
-spec wait_response(ReqId, WaitTime) -> Result when ReqId :: request_id(), WaitTime :: response_timeout(), Response :: {reply, Reply :: term()} | {error, {Reason :: term(), server_ref()}}, Result :: Response | timeout.
等待请求响应。
等待请求标识符 ReqId 的响应。请求必须通过 send_request/2 发送到 gen_statem 进程。此函数必须从调用 send_request/2 的同一进程中调用。
WaitTime 指定等待回复的时间。如果在指定时间内未收到回复,该函数将返回 timeout 且不进行清理。因此,可以重复调用该函数直到返回回复。
请参阅 call/3,了解如何处理请求以及 Reply 如何由 gen_statem 服务器发送。
如果 gen_statem 服务器进程在等待回复时死亡或终止,它将返回一个带有退出 Reason 的 error 返回值。
receive_response/2 和 wait_response/2 的区别在于,receive_response/2 会在超时时放弃请求,从而忽略潜在的未来响应,而 wait_response/2 则不会。
-spec wait_response(ReqIdCollection, WaitTime, Delete) -> Result when ReqIdCollection :: request_id_collection(), WaitTime :: response_timeout(), Delete :: boolean(), Response :: {reply, Reply :: term()} | {error, {Reason :: term(), server_ref()}}, Result :: {Response, Label :: term(), NewReqIdCollection :: request_id_collection()} | no_request | timeout.
等待集合中任何请求的响应。
等待 ReqIdCollection 中的响应。 ReqIdCollection 的所有请求标识符必须对应于使用 send_request/2 或 send_request/4 发出的请求,并且所有请求都必须由调用此函数的进程发出。
响应中的 Label 是与响应对应的请求标识符关联的 Label。请求标识符的 Label 在 将请求 id 添加到集合时或使用 send_request/4 发送请求时关联。
与 wait_response/2 相比,与特定请求标识符关联的返回结果或异常将包装在 3 元组 {Response, Label, NewReqIdCollection} 中。Response 是 wait_response/2 将产生的值,Label 是与特定请求标识符关联的值,NewReqIdCollection 是可能修改后的请求标识符集合。
如果 ReqIdCollection 为空,则返回 no_request。
如果在 WaitTime 过期之前没有收到响应,则返回 timeout。 在收到响应并通过 check_response()、 receive_response() 或 wait_response() 完成之前,可以根据需要多次继续等待响应。
receive_response/3 和 wait_response/3 之间的区别在于,receive_response/3 在超时时放弃请求,以便忽略潜在的未来响应,而 wait_response/3 则不会。
如果 Delete 为 true,则与 Label 的关联已从结果 NewReqIdCollection 中的 ReqIdCollection 中删除。 如果 Delete 为 false,则 NewReqIdCollection 将等于 ReqIdCollection。 请注意,删除关联不是免费的,并且包含已处理的请求的集合仍然可以被后续调用 wait_response/3、 check_response/3 和 receive_response/3 使用。
但是,如果不删除已处理的关联,则上述调用将无法检测到何时没有更多未完成的请求需要处理,因此您必须以其他方式跟踪此信息,而不是依赖 no_request 返回。 请注意,如果您将仅包含已处理或放弃的请求的关联的集合传递给此函数,它将始终阻塞直到 WaitTime 过期,然后返回 timeout。