1

I have a gen_server and i do not understand the following:

-module(myserver).
-behaviour(gen_server).
-record(state,{ count=0}).

handle_cast(Message,From,State=#state{count=C})->
     self() ! something,
     {noreply,State}.
handle_info(Message,State=#state{count=C})
      NewCount=case Message of 
                   0 -> C+1;
                   true ->C,
      {noreply,State#state{count=NewCount}}.

Given the above code i want to understand the following thing:
-assuming i enter handle_cast and i send something to the mailbox of the gen_server which fits the handle_info case, will the handle_info get called on a different thread right then and there ( self() ! Message while i am still evaluating the handle_cast?

What is the order in which things unfold ?

  • A

    • handle_cast enter
    • handle_cast send to own mailbox
    • handle_cast returns
    • handle_info gets triggered
  • B

    1. handle_cast enter
    2. handle_cast send to own mailbox
      3.handle_info gets triggered (concurrently) and may alter state
    3. handle_cast may or may not end before handle_info finishes , and can get state altered by handle_info
Bercovici Adrian
  • 8,794
  • 17
  • 73
  • 152
  • 1
    To improve your understanding of how `gen_server` works, consider reading [the `gen_server` behavior documentation](https://erlang.org/doc/design_principles/gen_server_concepts.html) and the answers to [Erlang/OTP behaviors for beginner](https://stackoverflow.com/q/5790804). – Steve Vinoski May 04 '20 at 11:30

2 Answers2

3

The answer is A. All handlers of gen_server - like handle_cast or handle_info - are always executed in the same single process. The message send to self within handle_cast will be received by the gen_server implementation after the handle_cast callback returns. Only then handle_info will be called, and handle_info will receive the state returned from handle_cast.

Generally, all gen_server handlers are always called sequentially, and the state returned by a handler is passed to the handler which is called next.

Wojtek Surowka
  • 20,535
  • 4
  • 44
  • 51
1

Here's an example that demonstrates that Wotek Surowka's answer is correct:

-module(s1).
-behavior(gen_server).
-compile(export_all).

start() ->
    gen_server:start_link(
      {local, ?MODULE}, 
      ?MODULE, 
      [],
      []
    ).

init(_Args) ->
    Count = 0,
    {ok, Count}.

handle_call(_Msg, _From, State) ->
    {reply, hello, State}.

handle_cast(_Msg, Count) ->
    io:format("Entered handle_cast()...~n"),

    self() ! hello,
    timer:sleep(10000), % Sleep for 10 seconds

    io:format("Returning from handle_cast()...~n"),
    {noreply, Count+1}.

handle_info(Msg, Count) ->
    io:format("Entered handle_info(), Msg= ~w~n", [Msg]),
    io:format("Count in handle_info() is: ~w~n", [Count]),
    io:format("Returning from handle_info()...~n"),
    {noreply, Count}.

go() ->
    spawn(
      fun() -> gen_server:cast(?MODULE, "some message") end
    ),
    ok.

In the example above, handle_cast() sleeps for 10 seconds, so if handle_info() executed asynchronously, it would have plenty of time to display its output before handle_cast() returned. Here are the results in the shell:

~/erlang_programs/gen_s/1server$ 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(s1).     
s1.erl:3: Warning: export_all flag enabled - all functions will be exported
{ok,s1}

2> s1:start().
{ok,<0.71.0>}

3> s1:go().   
Entered handle_cast()...
ok  <--- return value of go()
Returning from handle_cast()...
Entered handle_info(), Msg= hello
Count in handle_info() is: 1
Returning from handle_info()...

4> 

The output shows that handle_info() doesn't begin executing until after handle_cast() returns.

And, if you add some print statements to display the pid inside handle_cast() and handle_info(), you will see that the pid is the same:

-module(s1).
-behavior(gen_server).
-compile(export_all).

start() ->
    gen_server:start_link(
      {local, ?MODULE}, 
      ?MODULE, 
      [],
      []
    ).

init(_Args) ->
    Count = 0,
    {ok, Count}.

handle_call(_Msg, _From, State) ->
    {reply, hello, State}.

handle_cast(_Msg, Count) ->
    io:format("Entered handle_cast()...~n"),
    Self = self(),
    io:format("self() is: ~w~n", [Self]),

    Self ! hello,
    timer:sleep(10000), % Sleep for 10 seconds

    io:format("Returning from handle_cast()...~n"),
    {noreply, Count+1}.

handle_info(Msg, Count) ->
    io:format("Entered handle_info(), Msg= ~w~n", [Msg]),
    io:format("self() is: ~w~n", [self()]),
    io:format("Count in handle_info() is: ~w~n", [Count]),
    io:format("Returning from handle_info()...~n"),
    {noreply, Count}.

go() ->
    spawn(
      fun() -> gen_server:cast(?MODULE, "some message") end
    ),
    ok.

In the shell:

1> c(s1).     
s1.erl:3: Warning: export_all flag enabled - all functions will be exported
{ok,s1}

2> s1:start().
{ok,<0.71.0>}

3> s1:go().   
Entered handle_cast()...
ok  <---return value of go()
self() is: <0.71.0>
Returning from handle_cast()...
Entered handle_info(), Msg= hello
self() is: <0.71.0>
Count in handle_info() is: 1
Returning from handle_info()...

4> 
7stud
  • 46,922
  • 14
  • 101
  • 127