1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1996-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(gen_fsm). 21 22%%%----------------------------------------------------------------- 23%%% 24%%% This state machine is somewhat more pure than state_lib. It is 25%%% still based on State dispatching (one function per state), but 26%%% allows a function handle_event to take care of events in all states. 27%%% It's not that pure anymore :( We also allow synchronized event sending. 28%%% 29%%% If the Parent process terminates the Module:terminate/2 30%%% function is called. 31%%% 32%%% The user module should export: 33%%% 34%%% init(Args) 35%%% ==> {ok, StateName, StateData} 36%%% {ok, StateName, StateData, Timeout} 37%%% ignore 38%%% {stop, Reason} 39%%% 40%%% StateName(Msg, StateData) 41%%% 42%%% ==> {next_state, NewStateName, NewStateData} 43%%% {next_state, NewStateName, NewStateData, Timeout} 44%%% {stop, Reason, NewStateData} 45%%% Reason = normal | shutdown | Term terminate(State) is called 46%%% 47%%% StateName(Msg, From, StateData) 48%%% 49%%% ==> {next_state, NewStateName, NewStateData} 50%%% {next_state, NewStateName, NewStateData, Timeout} 51%%% {reply, Reply, NewStateName, NewStateData} 52%%% {reply, Reply, NewStateName, NewStateData, Timeout} 53%%% {stop, Reason, NewStateData} 54%%% Reason = normal | shutdown | Term terminate(State) is called 55%%% 56%%% handle_event(Msg, StateName, StateData) 57%%% 58%%% ==> {next_state, NewStateName, NewStateData} 59%%% {next_state, NewStateName, NewStateData, Timeout} 60%%% {stop, Reason, Reply, NewStateData} 61%%% {stop, Reason, NewStateData} 62%%% Reason = normal | shutdown | Term terminate(State) is called 63%%% 64%%% handle_sync_event(Msg, From, StateName, StateData) 65%%% 66%%% ==> {next_state, NewStateName, NewStateData} 67%%% {next_state, NewStateName, NewStateData, Timeout} 68%%% {reply, Reply, NewStateName, NewStateData} 69%%% {reply, Reply, NewStateName, NewStateData, Timeout} 70%%% {stop, Reason, Reply, NewStateData} 71%%% {stop, Reason, NewStateData} 72%%% Reason = normal | shutdown | Term terminate(State) is called 73%%% 74%%% handle_info(Info, StateName) (e.g. {'EXIT', P, R}, {nodedown, N}, ... 75%%% 76%%% ==> {next_state, NewStateName, NewStateData} 77%%% {next_state, NewStateName, NewStateData, Timeout} 78%%% {stop, Reason, NewStateData} 79%%% Reason = normal | shutdown | Term terminate(State) is called 80%%% 81%%% terminate(Reason, StateName, StateData) Let the user module clean up 82%%% always called when server terminates 83%%% 84%%% ==> the return value is ignored 85%%% 86%%% 87%%% The work flow (of the fsm) can be described as follows: 88%%% 89%%% User module fsm 90%%% ----------- ------- 91%%% start -----> start 92%%% init <----- . 93%%% 94%%% loop 95%%% StateName <----- . 96%%% 97%%% handle_event <----- . 98%%% 99%%% handle__sunc_event <----- . 100%%% 101%%% handle_info <----- . 102%%% 103%%% terminate <----- . 104%%% 105%%% 106%%% --------------------------------------------------- 107 108-include("logger.hrl"). 109 110-export([start/3, start/4, 111 start_link/3, start_link/4, 112 stop/1, stop/3, 113 send_event/2, sync_send_event/2, sync_send_event/3, 114 send_all_state_event/2, 115 sync_send_all_state_event/2, sync_send_all_state_event/3, 116 reply/2, 117 start_timer/2,send_event_after/2,cancel_timer/1, 118 enter_loop/4, enter_loop/5, enter_loop/6, wake_hib/7]). 119 120%% Internal exports 121-export([init_it/6, 122 system_continue/3, 123 system_terminate/4, 124 system_code_change/4, 125 system_get_state/1, 126 system_replace_state/2, 127 format_status/2]). 128 129%% logger callback 130-export([format_log/1]). 131 132-deprecated({start, 3, eventually}). 133-deprecated({start, 4, eventually}). 134-deprecated({start_link, 3, eventually}). 135-deprecated({start_link, 4, eventually}). 136-deprecated({stop, 1, eventually}). 137-deprecated({stop, 3, eventually}). 138-deprecated({send_event, 2, eventually}). 139-deprecated({sync_send_event, 2, eventually}). 140-deprecated({sync_send_event, 3, eventually}). 141-deprecated({send_all_state_event, 2, eventually}). 142-deprecated({sync_send_all_state_event, 2, eventually}). 143-deprecated({sync_send_all_state_event, 3, eventually}). 144-deprecated({reply, 2, eventually}). 145-deprecated({start_timer, 2, eventually}). 146-deprecated({send_event_after, 2, eventually}). 147-deprecated({cancel_timer, 1, eventually}). 148-deprecated({enter_loop, 4, eventually}). 149-deprecated({enter_loop, 5, eventually}). 150-deprecated({enter_loop, 6, eventually}). 151 152%%% --------------------------------------------------- 153%%% Interface functions. 154%%% --------------------------------------------------- 155 156-callback init(Args :: term()) -> 157 {ok, StateName :: atom(), StateData :: term()} | 158 {ok, StateName :: atom(), StateData :: term(), timeout() | hibernate} | 159 {stop, Reason :: term()} | ignore. 160-callback handle_event(Event :: term(), StateName :: atom(), 161 StateData :: term()) -> 162 {next_state, NextStateName :: atom(), NewStateData :: term()} | 163 {next_state, NextStateName :: atom(), NewStateData :: term(), 164 timeout() | hibernate} | 165 {stop, Reason :: term(), NewStateData :: term()}. 166-callback handle_sync_event(Event :: term(), From :: {pid(), Tag :: term()}, 167 StateName :: atom(), StateData :: term()) -> 168 {reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term()} | 169 {reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term(), 170 timeout() | hibernate} | 171 {next_state, NextStateName :: atom(), NewStateData :: term()} | 172 {next_state, NextStateName :: atom(), NewStateData :: term(), 173 timeout() | hibernate} | 174 {stop, Reason :: term(), Reply :: term(), NewStateData :: term()} | 175 {stop, Reason :: term(), NewStateData :: term()}. 176-callback handle_info(Info :: term(), StateName :: atom(), 177 StateData :: term()) -> 178 {next_state, NextStateName :: atom(), NewStateData :: term()} | 179 {next_state, NextStateName :: atom(), NewStateData :: term(), 180 timeout() | hibernate} | 181 {stop, Reason :: normal | term(), NewStateData :: term()}. 182-callback terminate(Reason :: normal | shutdown | {shutdown, term()} 183 | term(), StateName :: atom(), StateData :: term()) -> 184 term(). 185-callback code_change(OldVsn :: term() | {down, term()}, StateName :: atom(), 186 StateData :: term(), Extra :: term()) -> 187 {ok, NextStateName :: atom(), NewStateData :: term()}. 188-callback format_status(Opt, StatusData) -> Status when 189 Opt :: 'normal' | 'terminate', 190 StatusData :: [PDict | State], 191 PDict :: [{Key :: term(), Value :: term()}], 192 State :: term(), 193 Status :: term(). 194 195-optional_callbacks( 196 [handle_info/3, terminate/3, code_change/4, format_status/2]). 197 198%%% --------------------------------------------------- 199%%% Starts a generic state machine. 200%%% start(Mod, Args, Options) 201%%% start(Name, Mod, Args, Options) 202%%% start_link(Mod, Args, Options) 203%%% start_link(Name, Mod, Args, Options) where: 204%%% Name ::= {local, atom()} | {global, term()} | {via, atom(), term()} 205%%% Mod ::= atom(), callback module implementing the 'real' fsm 206%%% Args ::= term(), init arguments (to Mod:init/1) 207%%% Options ::= [{debug, [Flag]}] 208%%% Flag ::= trace | log | {logfile, File} | statistics | debug 209%%% (debug == log && statistics) 210%%% Returns: {ok, Pid} | 211%%% {error, {already_started, Pid}} | 212%%% {error, Reason} 213%%% --------------------------------------------------- 214start(Mod, Args, Options) -> 215 gen:start(?MODULE, nolink, Mod, Args, Options). 216 217start(Name, Mod, Args, Options) -> 218 gen:start(?MODULE, nolink, Name, Mod, Args, Options). 219 220start_link(Mod, Args, Options) -> 221 gen:start(?MODULE, link, Mod, Args, Options). 222 223start_link(Name, Mod, Args, Options) -> 224 gen:start(?MODULE, link, Name, Mod, Args, Options). 225 226stop(Name) -> 227 gen:stop(Name). 228 229stop(Name, Reason, Timeout) -> 230 gen:stop(Name, Reason, Timeout). 231 232send_event({global, Name}, Event) -> 233 catch global:send(Name, {'$gen_event', Event}), 234 ok; 235send_event({via, Mod, Name}, Event) -> 236 catch Mod:send(Name, {'$gen_event', Event}), 237 ok; 238send_event(Name, Event) -> 239 Name ! {'$gen_event', Event}, 240 ok. 241 242sync_send_event(Name, Event) -> 243 case catch gen:call(Name, '$gen_sync_event', Event) of 244 {ok,Res} -> 245 Res; 246 {'EXIT',Reason} -> 247 exit({Reason, {?MODULE, sync_send_event, [Name, Event]}}) 248 end. 249 250sync_send_event(Name, Event, Timeout) -> 251 case catch gen:call(Name, '$gen_sync_event', Event, Timeout) of 252 {ok,Res} -> 253 Res; 254 {'EXIT',Reason} -> 255 exit({Reason, {?MODULE, sync_send_event, [Name, Event, Timeout]}}) 256 end. 257 258send_all_state_event({global, Name}, Event) -> 259 catch global:send(Name, {'$gen_all_state_event', Event}), 260 ok; 261send_all_state_event({via, Mod, Name}, Event) -> 262 catch Mod:send(Name, {'$gen_all_state_event', Event}), 263 ok; 264send_all_state_event(Name, Event) -> 265 Name ! {'$gen_all_state_event', Event}, 266 ok. 267 268sync_send_all_state_event(Name, Event) -> 269 case catch gen:call(Name, '$gen_sync_all_state_event', Event) of 270 {ok,Res} -> 271 Res; 272 {'EXIT',Reason} -> 273 exit({Reason, {?MODULE, sync_send_all_state_event, [Name, Event]}}) 274 end. 275 276sync_send_all_state_event(Name, Event, Timeout) -> 277 case catch gen:call(Name, '$gen_sync_all_state_event', Event, Timeout) of 278 {ok,Res} -> 279 Res; 280 {'EXIT',Reason} -> 281 exit({Reason, {?MODULE, sync_send_all_state_event, 282 [Name, Event, Timeout]}}) 283 end. 284 285%% Designed to be only callable within one of the callbacks 286%% hence using the self() of this instance of the process. 287%% This is to ensure that timers don't go astray in global 288%% e.g. when straddling a failover, or turn up in a restarted 289%% instance of the process. 290 291%% Returns Ref, sends event {timeout,Ref,Msg} after Time 292%% to the (then) current state. 293start_timer(Time, Msg) -> 294 erlang:start_timer(Time, self(), {'$gen_timer', Msg}). 295 296%% Returns Ref, sends Event after Time to the (then) current state. 297send_event_after(Time, Event) -> 298 erlang:start_timer(Time, self(), {'$gen_event', Event}). 299 300%% Returns the remaining time for the timer if Ref referred to 301%% an active timer/send_event_after, false otherwise. 302cancel_timer(Ref) -> 303 case erlang:cancel_timer(Ref) of 304 false -> 305 receive {timeout, Ref, _} -> 0 306 after 0 -> false 307 end; 308 RemainingTime -> 309 RemainingTime 310 end. 311 312%% enter_loop/4,5,6 313%% Makes an existing process into a gen_fsm. 314%% The calling process will enter the gen_fsm receive loop and become a 315%% gen_fsm process. 316%% The process *must* have been started using one of the start functions 317%% in proc_lib, see proc_lib(3). 318%% The user is responsible for any initialization of the process, 319%% including registering a name for it. 320enter_loop(Mod, Options, StateName, StateData) -> 321 enter_loop(Mod, Options, StateName, StateData, self(), infinity). 322 323enter_loop(Mod, Options, StateName, StateData, {Scope,_} = ServerName) 324 when Scope == local; Scope == global -> 325 enter_loop(Mod, Options, StateName, StateData, ServerName,infinity); 326enter_loop(Mod, Options, StateName, StateData, {via,_,_} = ServerName) -> 327 enter_loop(Mod, Options, StateName, StateData, ServerName,infinity); 328enter_loop(Mod, Options, StateName, StateData, Timeout) -> 329 enter_loop(Mod, Options, StateName, StateData, self(), Timeout). 330 331enter_loop(Mod, Options, StateName, StateData, ServerName, Timeout) -> 332 Name = gen:get_proc_name(ServerName), 333 Parent = gen:get_parent(), 334 Debug = gen:debug_options(Name, Options), 335 HibernateAfterTimeout = gen:hibernate_after(Options), 336 loop(Parent, Name, StateName, StateData, Mod, Timeout, HibernateAfterTimeout, Debug). 337 338%%% --------------------------------------------------- 339%%% Initiate the new process. 340%%% Register the name using the Rfunc function 341%%% Calls the Mod:init/Args function. 342%%% Finally an acknowledge is sent to Parent and the main 343%%% loop is entered. 344%%% --------------------------------------------------- 345init_it(Starter, self, Name, Mod, Args, Options) -> 346 init_it(Starter, self(), Name, Mod, Args, Options); 347init_it(Starter, Parent, Name0, Mod, Args, Options) -> 348 Name = gen:name(Name0), 349 Debug = gen:debug_options(Name, Options), 350 HibernateAfterTimeout = gen:hibernate_after(Options), 351 case catch Mod:init(Args) of 352 {ok, StateName, StateData} -> 353 proc_lib:init_ack(Starter, {ok, self()}), 354 loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, Debug); 355 {ok, StateName, StateData, Timeout} -> 356 proc_lib:init_ack(Starter, {ok, self()}), 357 loop(Parent, Name, StateName, StateData, Mod, Timeout, HibernateAfterTimeout, Debug); 358 {stop, Reason} -> 359 gen:unregister_name(Name0), 360 proc_lib:init_ack(Starter, {error, Reason}), 361 exit(Reason); 362 ignore -> 363 gen:unregister_name(Name0), 364 proc_lib:init_ack(Starter, ignore), 365 exit(normal); 366 {'EXIT', Reason} -> 367 gen:unregister_name(Name0), 368 proc_lib:init_ack(Starter, {error, Reason}), 369 exit(Reason); 370 Else -> 371 Error = {bad_return_value, Else}, 372 proc_lib:init_ack(Starter, {error, Error}), 373 exit(Error) 374 end. 375 376%%----------------------------------------------------------------- 377%% The MAIN loop 378%%----------------------------------------------------------------- 379loop(Parent, Name, StateName, StateData, Mod, hibernate, HibernateAfterTimeout, Debug) -> 380 proc_lib:hibernate(?MODULE,wake_hib, 381 [Parent, Name, StateName, StateData, Mod, HibernateAfterTimeout, 382 Debug]); 383 384loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, Debug) -> 385 receive 386 Msg -> 387 decode_msg(Msg,Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, Debug, false) 388 after HibernateAfterTimeout -> 389 loop(Parent, Name, StateName, StateData, Mod, hibernate, HibernateAfterTimeout, Debug) 390 end; 391 392loop(Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout, Debug) -> 393 Msg = receive 394 Input -> 395 Input 396 after Time -> 397 {'$gen_event', timeout} 398 end, 399 decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout, Debug, false). 400 401wake_hib(Parent, Name, StateName, StateData, Mod, HibernateAfterTimeout, Debug) -> 402 Msg = receive 403 Input -> 404 Input 405 end, 406 decode_msg(Msg, Parent, Name, StateName, StateData, Mod, hibernate, HibernateAfterTimeout, Debug, true). 407 408decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout, Debug, Hib) -> 409 case Msg of 410 {system, From, Req} -> 411 sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, 412 [Name, StateName, StateData, Mod, Time, HibernateAfterTimeout], Hib); 413 {'EXIT', Parent, Reason} -> 414 terminate( 415 Reason, Name, undefined, Msg, Mod, StateName, StateData, Debug); 416 _Msg when Debug =:= [] -> 417 handle_msg(Msg, Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout); 418 _Msg -> 419 Debug1 = sys:handle_debug(Debug, fun print_event/3, 420 Name, {in, Msg, StateName}), 421 handle_msg(Msg, Parent, Name, StateName, StateData, 422 Mod, Time, HibernateAfterTimeout, Debug1) 423 end. 424 425%%----------------------------------------------------------------- 426%% Callback functions for system messages handling. 427%%----------------------------------------------------------------- 428system_continue(Parent, Debug, [Name, StateName, StateData, Mod, Time, HibernateAfterTimeout]) -> 429 loop(Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout, Debug). 430 431-spec system_terminate(term(), _, _, [term(),...]) -> no_return(). 432 433system_terminate(Reason, _Parent, Debug, 434 [Name, StateName, StateData, Mod, _Time, _HibernateAfterTimeout]) -> 435 terminate(Reason, Name, undefined, [], Mod, StateName, StateData, Debug). 436 437system_code_change([Name, StateName, StateData, Mod, Time, HibernateAfterTimeout], 438 _Module, OldVsn, Extra) -> 439 case catch Mod:code_change(OldVsn, StateName, StateData, Extra) of 440 {ok, NewStateName, NewStateData} -> 441 {ok, [Name, NewStateName, NewStateData, Mod, Time, HibernateAfterTimeout]}; 442 Else -> Else 443 end. 444 445system_get_state([_Name, StateName, StateData, _Mod, _Time, _HibernateAfterTimeout]) -> 446 {ok, {StateName, StateData}}. 447 448system_replace_state(StateFun, [Name, StateName, StateData, Mod, Time, HibernateAfterTimeout]) -> 449 Result = {NStateName, NStateData} = StateFun({StateName, StateData}), 450 {ok, Result, [Name, NStateName, NStateData, Mod, Time, HibernateAfterTimeout]}. 451 452%%----------------------------------------------------------------- 453%% Format debug messages. Print them as the call-back module sees 454%% them, not as the real erlang messages. Use trace for that. 455%%----------------------------------------------------------------- 456print_event(Dev, {in, Msg, StateName}, Name) -> 457 case Msg of 458 {'$gen_event', Event} -> 459 io:format(Dev, "*DBG* ~tp got event ~tp in state ~tw~n", 460 [Name, Event, StateName]); 461 {'$gen_all_state_event', Event} -> 462 io:format(Dev, 463 "*DBG* ~tp got all_state_event ~tp in state ~tw~n", 464 [Name, Event, StateName]); 465 {'$gen_sync_event', {From,_Tag}, Event} -> 466 io:format(Dev, 467 "*DBG* ~tp got sync_event ~tp " 468 "from ~tw in state ~tw~n", 469 [Name, Event, From, StateName]); 470 {'$gen_sync_all_state_event', {From,_Tag}, Event} -> 471 io:format(Dev, 472 "*DBG* ~tp got sync_all_state_event ~tp " 473 "from ~tw in state ~tw~n", 474 [Name, Event, From, StateName]); 475 {timeout, Ref, {'$gen_timer', Message}} -> 476 io:format(Dev, 477 "*DBG* ~tp got timer ~tp in state ~tw~n", 478 [Name, {timeout, Ref, Message}, StateName]); 479 {timeout, _Ref, {'$gen_event', Event}} -> 480 io:format(Dev, 481 "*DBG* ~tp got timer ~tp in state ~tw~n", 482 [Name, Event, StateName]); 483 _ -> 484 io:format(Dev, "*DBG* ~tp got ~tp in state ~tw~n", 485 [Name, Msg, StateName]) 486 end; 487print_event(Dev, {out, Msg, {To,_Tag}, StateName}, Name) -> 488 io:format(Dev, "*DBG* ~tp sent ~tp to ~tw~n" 489 " and switched to state ~tw~n", 490 [Name, Msg, To, StateName]); 491print_event(Dev, {noreply, StateName}, Name) -> 492 io:format(Dev, "*DBG* ~tp switched to state ~tw~n", 493 [Name, StateName]). 494 495handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTimeout) -> %No debug here 496 From = from(Msg), 497 case catch dispatch(Msg, Mod, StateName, StateData) of 498 {next_state, NStateName, NStateData} -> 499 loop(Parent, Name, NStateName, NStateData, Mod, infinity, HibernateAfterTimeout, []); 500 {next_state, NStateName, NStateData, Time1} -> 501 loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, []); 502 {reply, Reply, NStateName, NStateData} when From =/= undefined -> 503 reply(From, Reply), 504 loop(Parent, Name, NStateName, NStateData, Mod, infinity, HibernateAfterTimeout, []); 505 {reply, Reply, NStateName, NStateData, Time1} when From =/= undefined -> 506 reply(From, Reply), 507 loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, []); 508 {stop, Reason, NStateData} -> 509 terminate(Reason, Name, From, Msg, Mod, StateName, NStateData, []); 510 {stop, Reason, Reply, NStateData} when From =/= undefined -> 511 {'EXIT', R} = (catch terminate(Reason, Name, From, Msg, Mod, 512 StateName, NStateData, [])), 513 reply(From, Reply), 514 exit(R); 515 {'EXIT', {undef, [{Mod, handle_info, [_,_,_], _}|_]}} -> 516 ?LOG_WARNING(#{label=>{gen_fsm,no_handle_info}, 517 module=>Mod, 518 message=>Msg}, 519 #{domain=>[otp], 520 report_cb=>fun gen_fsm:format_log/1, 521 error_logger=>#{tag=>warning_msg}}), 522 loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, []); 523 {'EXIT', What} -> 524 terminate(What, Name, From, Msg, Mod, StateName, StateData, []); 525 Reply -> 526 terminate({bad_return_value, Reply}, 527 Name, From, Msg, Mod, StateName, StateData, []) 528 end. 529 530handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTimeout, Debug) -> 531 From = from(Msg), 532 case catch dispatch(Msg, Mod, StateName, StateData) of 533 {next_state, NStateName, NStateData} -> 534 Debug1 = sys:handle_debug(Debug, fun print_event/3, 535 Name, {noreply, NStateName}), 536 loop(Parent, Name, NStateName, NStateData, Mod, infinity, HibernateAfterTimeout, Debug1); 537 {next_state, NStateName, NStateData, Time1} -> 538 Debug1 = sys:handle_debug(Debug, fun print_event/3, 539 Name, {noreply, NStateName}), 540 loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, Debug1); 541 {reply, Reply, NStateName, NStateData} when From =/= undefined -> 542 Debug1 = reply(Name, From, Reply, Debug, NStateName), 543 loop(Parent, Name, NStateName, NStateData, Mod, infinity, HibernateAfterTimeout, Debug1); 544 {reply, Reply, NStateName, NStateData, Time1} when From =/= undefined -> 545 Debug1 = reply(Name, From, Reply, Debug, NStateName), 546 loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, Debug1); 547 {stop, Reason, NStateData} -> 548 terminate( 549 Reason, Name, From, Msg, Mod, StateName, NStateData, Debug); 550 {stop, Reason, Reply, NStateData} when From =/= undefined -> 551 {'EXIT', R} = (catch terminate(Reason, Name, From, Msg, Mod, 552 StateName, NStateData, Debug)), 553 _ = reply(Name, From, Reply, Debug, StateName), 554 exit(R); 555 {'EXIT', What} -> 556 terminate(What, Name, From, Msg, Mod, StateName, StateData, Debug); 557 Reply -> 558 terminate({bad_return_value, Reply}, 559 Name, From, Msg, Mod, StateName, StateData, Debug) 560 end. 561 562dispatch({'$gen_event', Event}, Mod, StateName, StateData) -> 563 Mod:StateName(Event, StateData); 564dispatch({'$gen_all_state_event', Event}, Mod, StateName, StateData) -> 565 Mod:handle_event(Event, StateName, StateData); 566dispatch({'$gen_sync_event', From, Event}, Mod, StateName, StateData) -> 567 Mod:StateName(Event, From, StateData); 568dispatch({'$gen_sync_all_state_event', From, Event}, 569 Mod, StateName, StateData) -> 570 Mod:handle_sync_event(Event, From, StateName, StateData); 571dispatch({timeout, Ref, {'$gen_timer', Msg}}, Mod, StateName, StateData) -> 572 Mod:StateName({timeout, Ref, Msg}, StateData); 573dispatch({timeout, _Ref, {'$gen_event', Event}}, Mod, StateName, StateData) -> 574 Mod:StateName(Event, StateData); 575dispatch(Info, Mod, StateName, StateData) -> 576 Mod:handle_info(Info, StateName, StateData). 577 578from({'$gen_sync_event', From, _Event}) -> From; 579from({'$gen_sync_all_state_event', From, _Event}) -> From; 580from(_) -> undefined. 581 582%% Send a reply to the client. 583reply({To, Tag}, Reply) -> 584 catch To ! {Tag, Reply}. 585 586reply(Name, From, Reply, Debug, StateName) -> 587 reply(From, Reply), 588 sys:handle_debug(Debug, fun print_event/3, Name, 589 {out, Reply, From, StateName}). 590 591%%% --------------------------------------------------- 592%%% Terminate the server. 593%%% --------------------------------------------------- 594 595-spec terminate(term(), _, _, _, atom(), _, _, _) -> no_return(). 596 597terminate(Reason, Name, From, Msg, Mod, StateName, StateData, Debug) -> 598 case erlang:function_exported(Mod, terminate, 3) of 599 true -> 600 case catch Mod:terminate(Reason, StateName, StateData) of 601 {'EXIT', R} -> 602 FmtStateData = format_status(terminate, Mod, get(), StateData), 603 error_info( 604 R, Name, From, Msg, StateName, FmtStateData, Debug), 605 exit(R); 606 _ -> 607 ok 608 end; 609 false -> 610 ok 611 end, 612 case Reason of 613 normal -> 614 exit(normal); 615 shutdown -> 616 exit(shutdown); 617 {shutdown,_}=Shutdown -> 618 exit(Shutdown); 619 _ -> 620 FmtStateData1 = format_status(terminate, Mod, get(), StateData), 621 error_info( 622 Reason, Name, From, Msg, StateName, FmtStateData1, Debug), 623 exit(Reason) 624 end. 625 626error_info(Reason, Name, From, Msg, StateName, StateData, Debug) -> 627 Log = sys:get_log(Debug), 628 ?LOG_ERROR(#{label=>{gen_fsm,terminate}, 629 name=>Name, 630 last_message=>Msg, 631 state_name=>StateName, 632 state_data=>StateData, 633 log=>Log, 634 reason=>Reason, 635 client_info=>client_stacktrace(From)}, 636 #{domain=>[otp], 637 report_cb=>fun gen_fsm:format_log/1, 638 error_logger=>#{tag=>error}}), 639 ok. 640 641client_stacktrace(undefined) -> 642 undefined; 643client_stacktrace({Pid,_Tag}) -> 644 client_stacktrace(Pid); 645client_stacktrace(Pid) when is_pid(Pid), node(Pid) =:= node() -> 646 case process_info(Pid, [current_stacktrace, registered_name]) of 647 undefined -> 648 {Pid,dead}; 649 [{current_stacktrace, Stacktrace}, {registered_name, []}] -> 650 {Pid,{Pid,Stacktrace}}; 651 [{current_stacktrace, Stacktrace}, {registered_name, Name}] -> 652 {Pid,{Name,Stacktrace}} 653 end; 654client_stacktrace(Pid) when is_pid(Pid) -> 655 {Pid,remote}. 656 657 658format_log(#{label:={gen_fsm,terminate}, 659 name:=Name, 660 last_message:=Msg, 661 state_name:=StateName, 662 state_data:=StateData, 663 log:=Log, 664 reason:=Reason, 665 client_info:=ClientInfo}) -> 666 Reason1 = 667 case Reason of 668 {undef,[{M,F,A,L}|MFAs]} -> 669 case code:is_loaded(M) of 670 false -> 671 {'module could not be loaded',[{M,F,A,L}|MFAs]}; 672 _ -> 673 case erlang:function_exported(M, F, length(A)) of 674 true -> 675 Reason; 676 false -> 677 {'function not exported',[{M,F,A,L}|MFAs]} 678 end 679 end; 680 _ -> 681 Reason 682 end, 683 {ClientFmt,ClientArgs} = format_client_log(ClientInfo), 684 {"** State machine ~tp terminating \n" ++ 685 get_msg_str(Msg) ++ 686 "** When State == ~tp~n" 687 "** Data == ~tp~n" 688 "** Reason for termination ==~n** ~tp~n" ++ 689 case Log of 690 [] -> []; 691 _ -> "** Log ==~n** ~tp~n" 692 end ++ ClientFmt, 693 [Name|error_logger:limit_term(get_msg(Msg))] ++ 694 [StateName, 695 error_logger:limit_term(StateData), 696 error_logger:limit_term(Reason1) | 697 case Log of 698 [] -> []; 699 _ -> [[error_logger:limit_term(D) || D <- Log]] 700 end] ++ ClientArgs}; 701format_log(#{label:={gen_fsm,no_handle_info}, 702 module:=Mod, 703 message:=Msg}) -> 704 {"** Undefined handle_info in ~p~n" 705 "** Unhandled message: ~tp~n", 706 [Mod, error_logger:limit_term(Msg)]}. 707 708get_msg_str({'$gen_event', _Event}) -> 709 "** Last event in was ~tp~n"; 710get_msg_str({'$gen_sync_event', _From, _Event}) -> 711 "** Last sync event in was ~tp from ~tw~n"; 712get_msg_str({'$gen_all_state_event', _Event}) -> 713 "** Last event in was ~tp (for all states)~n"; 714get_msg_str({'$gen_sync_all_state_event', _From, _Event}) -> 715 "** Last sync event in was ~tp (for all states) from ~tw~n"; 716get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}) -> 717 "** Last timer event in was ~tp~n"; 718get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) -> 719 "** Last timer event in was ~tp~n"; 720get_msg_str(_Msg) -> 721 "** Last message in was ~tp~n". 722 723get_msg({'$gen_event', Event}) -> [Event]; 724get_msg({'$gen_sync_event', {From,_Tag}, Event}) -> [Event,From]; 725get_msg({'$gen_all_state_event', Event}) -> [Event]; 726get_msg({'$gen_sync_all_state_event', {From,_Tag}, Event}) -> [Event,From]; 727get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> [{timeout, Ref, Msg}]; 728get_msg({timeout, _Ref, {'$gen_event', Event}}) -> [Event]; 729get_msg(Msg) -> [Msg]. 730 731format_client_log(undefined) -> 732 {"", []}; 733format_client_log({From,dead}) -> 734 {"** Client ~p is dead~n", [From]}; 735format_client_log({From,remote}) -> 736 {"** Client ~p is remote on node ~p~n", [From, node(From)]}; 737format_client_log({_From,{Name,Stacktrace}}) -> 738 {"** Client ~tp stacktrace~n" 739 "** ~tp~n", 740 [Name, error_logger:limit_term(Stacktrace)]}. 741 742%%----------------------------------------------------------------- 743%% Status information 744%%----------------------------------------------------------------- 745format_status(Opt, StatusData) -> 746 [PDict, SysState, Parent, Debug, [Name, StateName, StateData, Mod, _Time, _HibernateAfterTimeout]] = 747 StatusData, 748 Header = gen:format_status_header("Status for state machine", 749 Name), 750 Log = sys:get_log(Debug), 751 Specific = 752 case format_status(Opt, Mod, PDict, StateData) of 753 S when is_list(S) -> S; 754 S -> [S] 755 end, 756 [{header, Header}, 757 {data, [{"Status", SysState}, 758 {"Parent", Parent}, 759 {"Logged events", Log}, 760 {"StateName", StateName}]} | 761 Specific]. 762 763format_status(Opt, Mod, PDict, State) -> 764 DefStatus = case Opt of 765 terminate -> State; 766 _ -> [{data, [{"StateData", State}]}] 767 end, 768 case erlang:function_exported(Mod, format_status, 2) of 769 true -> 770 case catch Mod:format_status(Opt, [PDict, State]) of 771 {'EXIT', _} -> DefStatus; 772 Else -> Else 773 end; 774 _ -> 775 DefStatus 776 end. 777