檢視原始碼 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
事件佇列模型足以模擬具有選擇性接收的正常程序訊息佇列。延遲事件相當於不在 receive 陳述式中比對它,而變更狀態相當於進入新的 receive 陳述式。
事件插入
狀態回呼 可以使用 轉換動作 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()
會產生一個類型為call
的get_count
事件,此事件會在所有狀態中被處理,並回覆目前的計數值。 - 未知的事件會被忽略並丟棄。
- 有樣板程式碼用於啟動、停止、終止、程式碼變更、初始化,以及設定回呼模式為
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
用於所有狀態。
函數 Module:callback_mode/0
會在啟動 gen_statem
時、程式碼變更後以及在使用任何動作 change_callback_module
、push_callback_module
或 pop_callback_module
變更回呼模組後呼叫。結果會快取以供後續呼叫 狀態回呼。
-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/0
hibernate/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 log。
新的關聯可能會新增到狀態映射中,恕不另行通知。
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
,則會在進入 receive
以等待新的外部事件之前,呼叫 proc_lib:hibernate/3
來休眠 gen_statem
。
還有一個伺服器啟動選項 {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
。
當進入第一個 狀態時,會執行 Actions
,就像執行 狀態回調一樣,只是動作 postpone
會強制設為 false
,因為沒有事件可以延後。
對於不成功的初始化,應使用 {stop, Reason}
、{error, Reason}
或 ignore
;請參閱 start_link/3,4
。
自 OTP 26.0 起 已允許 {error, Reason}
。
{ok, ...}
元組自 OTP 19.1 起存在,在此之前它們沒有 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
。時間單位為 millisecond
(毫秒)。
目前可用的值:
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]}
- 透過使用Reason
和NewData
(如果指定) 呼叫Module:terminate/3
來終止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}
更明確地取消它。
計時器的 EventContent
可以使用 {state_timeout, update, NewEventContent}
動作來更新,而不會影響逾時時間。
-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/0
event_timeout/0
設定為Time
,並帶有EventContent
和逾時選項Options
。{{timeout,Name}, Time, EventContent [, Options]}
- 將transition_option/0
generic_timeout/0
設定為逾時Name
的Time
,並帶有EventContent
和逾時選項Options
。
自 OTP 20.0 起.{state_timeout, Time, EventContent [, Options]}
- 將transition_option/0
state_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
為自己產生的逾時事件類型
逾時類型 | 動作 | 事件類型 |
---|---|---|
事件逾時 |
|
|
通用逾時 |
|
|
狀態逾時 |
|
|
簡而言之;設定具有 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().
由動作設定的狀態轉換選項。
這些決定在狀態轉換期間會發生什麼。當 狀態回呼 處理完事件並傳回時,就會發生狀態轉換。以下是狀態轉換的步驟順序
所有傳回的 動作 都會按照出現順序處理。在此步驟中,會傳送由任何
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/0
next_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
來變更回呼模組,請注意,始終是目前的回呼模組會收到此回呼呼叫。目前的回呼模組處理目前的狀態和資料應該不足為奇,但它必須能夠處理甚至是不熟悉的狀態和資料的部分。
在監督程式的子規格中,有一個模組列表,建議只包含回呼模組。對於具有多個回呼模組的 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()).
狀態回呼,處理狀態 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
終止的原因。如果是因為另一個回呼函數已在 Actions
中傳回停止元組 {stop, Reason}
,則 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.
將事件發送至伺服器。
將非同步 cast
事件傳送到 gen_statem
ServerRef
,並立即傳回 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
,了解如何處理請求以及 gen_statem
伺服器如何傳送 Reply
。
如果在呼叫此函數時 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
,了解如何處理請求以及 gen_statem
伺服器如何傳送 Reply
。
如果在等待回覆時 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
回覆。
此函數可被 gen_statem
回呼函數使用,以明確地將一個或多個回覆傳送給正在等待 call
請求回覆的程序,當從狀態回呼函數返回 reply_action/0
不切實際或不可能時。
注意
使用此函數傳送的回覆在
sys
偵錯輸出中不可見。
傳送 call
Reply
至 From
。
此函數可被 gen_statem
回呼函數使用,以明確地將回覆傳送給正在等待 call
請求回覆的程序,當從狀態回呼函數返回 reply_action/0
不切實際或不可能時。
注意
使用此函數傳送的回覆在
sys
偵錯輸出中不可見。
-spec reqids_add(ReqId :: request_id(), Label :: term(), ReqIdCollection :: request_id_collection()) -> NewReqIdCollection :: request_id_collection().
在集合中儲存請求識別碼。
儲存 ReqId
並將 Label
與請求識別碼關聯,方法是將此資訊新增到 ReqIdCollection
並傳回產生的請求識別碼集合。
-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
,了解如何處理請求以及 gen_statem
伺服器如何傳送 Reply
。
伺服器的 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
程序。由於它未連結到呼叫程序,因此監管程式無法使用此啟動函數來啟動子程序。
如需引數和傳回值的描述,請參閱 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
必須是監管樹的一部分時,這一點至關重要,以便它連結到其監管程式。
產生的 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}
- 在從Module:init/1
傳回之前,允許gen_statem
程序花費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
程序,並以原子方式設置對新建立程序的監視器。
由於已啟動的程序未連結到呼叫程序,因此監督者無法使用此啟動函數來啟動子程序。
有關引數和傳回值的說明,請參閱 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
,了解如何處理請求以及 gen_statem
伺服器如何傳送 Reply
。
如果在等待回覆時 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
。