Search code examples
eventserlangerlang-otpfsm

How to call using gen_event in Erlang?


I am using a gen_event behaviour and when i am trying to issue a gen_event:call i get the following error:

> =CRASH REPORT==== 22-Dec-2019::19:17:43.030000 ===   crasher:
>     initial call: gen_event:init_it/6
>     pid: <0.215.0>
>     registered_name: hev
>     exception exit: {undef,[{fm,state,[<0.215.0>],[]},
>                             {erl_eval,do_apply,6,
>                                       [{file,"erl_eval.erl"},{line,684}]},
>                             {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
>                             {shell,eval_exprs,7,
>                                    [{file,"shell.erl"},{line,642}]},
>                             {shell,eval_loop,3,
>                                    [{file,"shell.erl"},{line,627}]}]}
>       in function  gen_event:terminate_server/4 (gen_event.erl, line 354)
>     ancestors: [<0.212.0>]
>     message_queue_len: 1
>     messages: [{'EXIT',<0.212.0>,normal}]
>     links: []
>     dictionary: []
>     trap_exit: true
>     status: running
>     heap_size: 610
>     stack_size: 27
>     reductions: 279   neighbours:

My event manager and event handler get spawned and i can successfully issue notify (i get ok back) but i can not call:

Module

-module(hev).
-export([start/0,append/2,state/1]).
-export([init/1,terminate/2,code_change/3,handle_call/2,handle_event/2]).
-record(state,{
    xs=[]
}).
-behaviour(gen_event).
%callbacks
init([])->
    {ok,#state{xs=[1]}}.

**API**

start()->
    {ok,Pid}=gen_event:start_link({local,?MODULE}),
    gen_event:add_handler(Pid,some_handler,[]),
    Pid.
append(Pid,Elem)->
    gen_event:notify(Pid,{append,Elem}).
state(Pid)->
    gen_event:call(Pid,state).

Handlers

handle_event({append,Elem},State=#state{xs=XS})->
    {ok,#state{xs=[Elem|XS]}};

handle_call(state,State})->     
    {ok,State,State};
handle_call(Event,State)->
    {ok,nada_for_you,State}.

P.S I have not posted all the requried methods (code_change,terminate..etc) but they exist.


Solution

  • I have not posted all the requried methods (code_change,terminate..etc) but they exist.

    1) They are optional anyway. Check the big green Notes in the docs, e.g. terminate().

    2) As for your error message:

    pid: <0.215.0>
    registered_name: hev
    exception exit: {undef,[{fm,state,[<0.215.0>],[]},
    

    it seems to be saying that there was a process (with pid=<0.215.0>), which was registered with the name hev, that tried to execute a function named fm:state() with one argument but there was no function fm:state/1 defined anywhere, hence the undef exception. Because you posted no module named fm, the error makes no sense in relation to the code you posted.

    3) Your code also specifies a module named some_handler that does not exist:

    gen_event:add_handler(Pid,some_handler,[]),
    

    4) You have a basic syntax error here:

     handle_call(state,State})-> 
    

    5) You are calling a couple of functions with the wrong number of arguments.

    You need to be more diligent about posting code that will actually produce the error you experienced.

    Here's a simple example using gen_event to create a counter:

    -module(counter).
    -behaviour(gen_event).
    -compile(export_all).
    
    %% Callback functions:
    
    init(StartingCount) ->  % Called by gen_event:add_handler()
        State = StartingCount,
        {ok, State}.
    
    terminate(_Reason, State) ->
        io:format("Terminating state was: ~w~n", [State]).
    
    % Calls to gen_event:notify() cause this function to execute:
    handle_event({increase, Change}, State) -> 
        NewState = State+Change,
        {ok, NewState};
    handle_event({decrease, Change}, State) ->
        NewState = State-Change,
        {ok, NewState}.
    
    % Calls to gen_event:call() cause this function to execute:
    handle_call(get_count, State) ->  
        Reply = io_lib:format("Reply from handle_call(): count is ~w~n", [State]),
        {ok, Reply, State};
    handle_call({increase_and_get_count, Change}, State) ->
        NewState = State+Change,
        Reply = io_lib:format("Reply from handle_call(): count is ~w~n", [NewState]),
        {ok, Reply, NewState}.
    
    
    %% User interface functions:
    
    start(StartingCount) ->
        ServerName = gen_event_counter,
        CallbackModule = counter,
    
        {ok, _Pid} = gen_event:start_link({local, ServerName}),  
        %Name of process running gen_event server is: gen_event_counter
    
        ok = gen_event:add_handler(ServerName, CallbackModule, StartingCount). 
        %StartingCount is passed to init() callback
    
    
    stop() ->
        ok = gen_event:stop(gen_event_counter),
        stopped.
    
    send_request_with_notify(Request) -> 
        gen_event:notify(gen_event_counter, Request). % returns immediately, does not wait for a reply.
        % Request = {increase, 1}, {decrease, 2}, etc. 
        % Request is passed as first arg to handle_event().
    
    
    send_request_with_call(Request) -> 
        Reply = gen_event:call(gen_event_counter, counter, Request), % waits for a reply  
        % Request is passed as first arg to handle_call()
        io:format("send_request_with_call() returned => ~s", [Reply]).
    

    In the shell:

    ~/erlang_programs/gen_event$ erl
    Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
    Eshell V9.3  (abort with ^G)
    
    1> c(counter).                                                 
    counter.erl:3: Warning: export_all flag enabled - all functions will be exported
    {ok,counter}
    
    2> counter:start(0).                                           
    ok
    
    3> counter:send_request_with_call(get_count).                  
    send_request_with_call() returned => Reply from handle_call(): count is 0
    ok
    
    4> counter:send_request_with_notify({increase, 2}).            
    ok
    
    5> counter:send_request_with_call(get_count).      
    send_request_with_call() returned => Reply from handle_call(): count is 2
    ok
    
    6> counter:send_request_with_call({increase_and_get_count, 5}).
    send_request_with_call() returned => Reply from handle_call(): count is 7
    ok
    
    7> counter:stop().                                             
    Terminating state was: 7
    stopped
    
    8> 
    

    After fixing all the errors in your code:

    -module(hev).
    -compile(export_all).
    -behaviour(gen_event).
    
    -record(state,{
        xs=[]
    }).
    
    %callbacks
    init(no_args)->
        {ok, #state{xs=[1]} }.
    
    handle_event({append,Elem}, #state{xs=XS} ) ->
        io:format("hev:handle_event() called~n"),
        {ok, #state{xs=[Elem|XS]}}.
    
    handle_call(get_state, State)->     
        Reply = State,
        {ok, Reply, State};
    handle_call(_Other, State)->
        Reply = nada_for_you,
        {ok, Reply, State}.
    
    %**API**
    
    start()->
        gen_event:start_link({local, ?MODULE}),  %Sets the gen_event server name to ?MODULE
    
        %                     Server   Callback  Args for
        %                     Name     module    init()
        gen_event:add_handler(?MODULE, ?MODULE, no_args). 
        %Tells the gen_event server named ?MODULE to look for the callback functions 
        %in a module also named ?MODULE
    
    append(Elem)->
        %                Server    Request
        %                Name      (matches against 1st arg in handle_event() )
        gen_event:notify(?MODULE, {append, Elem}).
    
    get_state()->
        %              Server   Calback  Request
        %              Name     module   (matches against 1st arg in handle_call() )
        gen_event:call(?MODULE, ?MODULE, get_state).
    
    other_calls() ->
        gen_event:call(?MODULE, ?MODULE, {set_state, [1, 2, 3]}).
    
    
    stop() ->
        ok = gen_event:stop(?MODULE),
        stopped.
    

    In the shell:

    ~/erlang_programs/gen_event$ erl
    Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
    Eshell V9.3  (abort with ^G)
    
    1> c(hev).         
    hev.erl:2: Warning: export_all flag enabled - all functions will be exported
    {ok,hev}
    
    2> hev:start().    
    ok
    
    3> hev:get_state().
    {state,[1]}
    
    4> hev:append(45). 
    ok
    hev:handle_event() called
    
    5> hev:get_state().
    {state,[45,1]}
    
    6> hev:other_calls().
    nada_for_you
    
    7> hev:stop().
    stopped
    
    8> 
    

    Note that notify() causes the handle_event() function in all the modules that were added with add_handler() to execute, whereas call() targets a specific module's handle_call() function.