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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With