Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If the init/1 function in a gen_server process sends a message to itself, is it guaranteed to arrive before any other message?

There is a pattern I've seen occasionally where the init/1 function of a gen_server process will send a message to itself signalling that it should be initialized. The purpose of this is for the gen_server process to initialize itself asynchronously so that the process spawning it doesn't have to wait. Here is an example:

-module(test).
-compile(export_all).

init([]) ->
    gen_server:cast(self(), init),
    {ok, {}}.

handle_cast(init, {}) ->
    io:format("initializing~n"),
    {noreply, lists:sum(lists:seq(1,10000000))};
handle_cast(m, X) when is_integer(X) ->
    io:format("got m. X: ~p~n", [X]),
    {noreply, X}.

b() ->
    receive P -> {} end,
    gen_server:cast(P, m),
    b().

test() ->
    B = spawn(fun test:b/0),
    {ok, A} = gen_server:start_link(test,[],[]),
    B ! A.

The process assumes that the init message will be received before any other message - otherwise it will crash. Is it possible for this process to get the m message before the init message?


Let's assume there's no process sending messages to random pids generated by list_to_pid, since any application doing this will probably not work at all, regardless of the answer to this question.

like image 574
Dog Avatar asked Aug 01 '13 14:08

Dog


1 Answers

Theoretical answer to the question is it possible for a process to get a message before the init message? is YES. But practically (when no process is doing list_to_pid and sending a message) to this process the answer is NO provided the gen_server is not a registered process.

This is because the return of gen_server:start_link ensures that callback init of gen_server is executed. Thus initialize message is the first message in the process message queue before any other process gets the Pid to send a message. Thus your process is safe and does not receive any other message before init.

But same does not go true for registered process as there can be a process which might be sending message to the gen_server using registered name even before it completes callback init function. Lets consider this test function.

test() ->
    Times = lists:seq(1,1000),
    spawn(gen_server, start_link,[{local, ?MODULE}, ?MODULE, [], []]),
    [gen_server:cast(?MODULE, No) || No <-Times].

The sample output is

1> async_init:test().
Received:356
Received:357
[ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,
 ok,ok,ok,ok,ok,ok,ok,ok,ok,ok|...]
Received:358
Received:359
2> Received:360
2> Received:361
...
2> Received:384
2> Received:385
2> Initializing
2> Received:386
2> Received:387
2> Received:388
2> Received:389 
...

You can see that gen_server received messages from 356 to 385 messages before initialize. Thus the async callback does not work in registered name scenario.

This can be solved by two ways

1.Register the process after Pid is returned.

 start_link_reg() ->
      {ok, Pid} = gen_server:start(?MODULE, [], []),
      register(?MODULE, Pid).

2.Or in handle_cast for init message register the process.

handle_cast(init, State) ->
    register(?MODULE, self()),
    io:format("Initializing~n"),
    {noreply, State};

The sample output after this change is

1> async_init:test().
Initializing
Received:918
Received:919
[ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,ok,
 ok,ok,ok,ok,ok,ok,ok,ok,ok,ok|...]
Received:920
2> Received:921
2> Received:922
...

Thus sending a message to itself for initializing does not ensure that it is the first message that it receives but with bit of changes in the code (and design) can ensure that it is the first to be executed.

like image 157
Vinod Avatar answered Sep 20 '22 06:09

Vinod