【问题标题】:Can not run fsm in Erlang无法在 Erlang 中运行 fsm
【发布时间】:2025-12-02 21:00:01
【问题描述】:

您好,我正在尝试在与调用者不同的进程中使用gen_statem 运行fsm。我还尝试将调用者保留在其状态参数中,以便在每次状态更改后对其进行更新。 我不断收到此错误:

  ** State machine <0.123.0> terminating
** Last event = {internal,init_state}
** When server state  = {sitting_home,{state,"None",0,0,[<0.116.0>]}}
** Reason for termination = error:{'function not exported',
                                      {fsm,callback_mode,0}}
** Callback mode = undefined
** Stacktrace =
**  [{gen_statem,call_callback_mode,1,[{file,"gen_statem.erl"},{line,1610}]},
     {gen_statem,enter,7,[{file,"gen_statem.erl"},{line,679}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,249}]}]

=CRASH REPORT==== 12-Dec-2019::00:14:42.717000 ===
  crasher:
    initial call: fsm:init/1
    pid: <0.123.0>
    registered_name: []
    exception error: undefined function fsm:callback_mode/0
      in function  gen_statem:call_callback_mode/1 (gen_statem.erl, line 1610)
      in call from gen_statem:enter/7 (gen_statem.erl, line 679)
    ancestors: [<0.116.0>]
    message_queue_len: 0
    messages: []
    links: []
    dictionary: []
    trap_exit: false
    status: running
    heap_size: 1598
    stack_size: 27
    reductions: 5805
  neighbours:

我查看了 erlang 文档,我发现没有回调 mode 需要实现 here

模块

-module(fsm).
-record(state,{
    current="None",
    intvCount=0,
    jobCount=0,
    monitor
}).
-export([init/1,start/0]).
-export([hire/2,fire/1,interview/2]).

-behaviour(gen_statem).


start()->
    gen_statem:start(?MODULE,[self()],[]).

init(Monitor)->
    {ok,sitting_home,#state{current="None",jobCount=0,intvCount=0,monitor=Monitor}}.

sitting_home({intv,Company},State=#state{intvCount=C,monitor=M})->
     gen_statem:reply(M,"Got an interview from:"++Company++" going interviewing"),
     {next_state,interviewing,State#state{intvCount=C+1}};
sitting_home(Event,State)->
     gen_statem:reply(State#state.monitor,{unexpected , Event}),
     {next_state,sitting_home,State}.

interviewing({rejected,Company},State)->
    gen_statem:reply("Booh got rejected by :"++ Company),
    {next_state,sitting_home,State};
interviewing({accepted,Company},State=#state{jobCount=J})->
    gen_statem:reply("Hooray got accepted"),
    {next_state,working,State#state{jobCount=J+1,current=Company}};
interviewing(_,State)->
    gen_statem:reply("Unknown message"),
    {next_state,interviewing,State}.


working(fire,State=#state{current=C})->
    gen_statem:reply("Unexpected event"),
    {next_state,working,State#state{current="None"}};
working(Event,State)->
        gen_statem:reply("Unexpected event"),
        {next_state,working,State}.

活动

hire(Company,PID)->
    gen_statem:sync_send_event(PID,{hire,self(),Company},0).
fire(PID)->
    gen_statem:sync_send_event(PID,{fire,self()},0).
interview(Company,PID)->
    gen_state:sync_send_event(PID,{intv,Company},0).

P.S我使用 gen_statem 是因为 shell 告诉我 gen_fsm 已弃用。我不知道 sync_send_event 的等效项。这可能是问题吗?

【问题讨论】:

    标签: erlang erlang-otp fsm


    【解决方案1】:
    1. Module:callback_mode is documented 在您链接的页面上。当您为每个状态(sitting_homeinterviewing 等)都有一个函数时,它应该是

      callback_mode() -> state_functions.
      
    2. 状态函数采用 3 个参数,而不是 2 个。您缺少第一个参数,EventType,在您的情况下应该是 {call, From}

    3. sync_send_event 的等价物可以是 call,但是你的状态函数应该总是回复。

    4. 您可以在回调的返回值中组合replynext_state,例如

      working(Event,State)->
          gen_statem:reply("Unexpected event"),
          {next_state,working,State}.
      

      可以变成

      working({call, From}, Event, State)->
          {next_state, working, State, {reply, From, "Unexpected event"}}.
      

      即使你不这样做,replyneeds a From argument:

      working({call, From}, Event, State)->
          gen_statem:reply(From, "Unexpected event"),
          {next_state,working,State}.
      

    【讨论】:

    • 好的,所以使用 gen_statem 我的所有状态都必须有 3 个参数,其中一个是 {Call/cast,From}。如果是这种情况,我仍然没有在返回中得到 {reply,From,Message} 参数。此问题是否会发出 gen_statem:reply 进行回复并返回状态?
    • 1. “其中之一是 {Call/cast,From}”{call, From}/cast/timeout/等。但不是{cast,From}。 2. 是的,您可以将一个动作或动作列表作为元组的第四个组成部分。不仅是回复,还允许其他操作。见erlang.org/doc/man/gen_statem.html#type-event_handler_result
    • 但是我在调​​用 reply 时没有得到语法:{ function name, argument} 导致调用该方法。是特定于 gen_statem 的约定还是特定于 erlang 的?跨度>
    • 特定于gen_statem。而{reply, From, "Unexpected event"}这里是一个动作(erlang.org/doc/man/gen_statem.html#type-action),只和函数名重合。