spawn_link not working? - erlang

I'm using spawn_link but doesn't understand its behavior. Consider the following code:
-module(test).
-export([try_spawn_link/0]).
try_spawn_link() ->
spawn(fun() ->
io:format("parent: ~p~n", [Parent = self()]),
Client = spawn_link(fun() ->
io:format("child: ~p~n", [self()]),
spawn_link_loop(Parent)
end),
spawn_link_loop(Client)
end).
spawn_link_loop(Peer) ->
receive
quit ->
exit(normal);
Any ->
io:format("~p receives ~p~n", [self(), Any])
end,
spawn_link_loop(Peer).
From the Erlang documentation, a link is created between the calling process and the new process, atomically. However, I tested as follows and didn't notice the effect of the link.
1> test:try_spawn_link().
parent: <0.34.0>
<0.34.0>
child: <0.35.0>
2> is_process_alive(pid(0,34,0)).
true
3> is_process_alive(pid(0,35,0)).
true
4> pid(0,35,0) ! quit.
quit
5> is_process_alive(pid(0,35,0)).
false
6> is_process_alive(pid(0,34,0)).
true
1> test:try_spawn_link().
parent: <0.34.0>
<0.34.0>
child: <0.35.0>
2> is_process_alive(pid(0,34,0)).
true
3> is_process_alive(pid(0,35,0)).
true
4> pid(0,34,0) ! quit.
quit
5> is_process_alive(pid(0,35,0)).
true
6> is_process_alive(pid(0,34,0)).
false
In my understanding, if one peer of the link exits, the other peer exits (or is notified to exit). But the results seem different from my understanding.
EDIT: thanks to the answers of legoscia and Pascal.

It is because you have chosen to use exit(normal). In this case the other process will not stop. If you use for example exit(killed) then you will get the behavior you are expecting.
You can use monitor to get informed about normal termination.

As described in the "Error handling" section of the Processes chapter of the Erlang reference manual, a linked process exiting causes its linked processes to exit only if the exit reason is not normal. That's why OTP extensively uses the shutdown exit reason.

Related

Why dont I get any terminal output after hot code loading in Erlang?

While trying to understand how to work with servers and hot code loading I stumbled over a problem which I have stripped down to the following code:
server.erl
-module(server).
-export([start/0, connect/1]).
start() ->
{ok, Listen} = gen_tcp:listen(8080, [binary, {packet, raw}, {active, true}]),
spawn(?MODULE, connect, [Listen]).
connect(Listen) ->
{ok, Socket} = gen_tcp:accept(Listen),
spawn(?MODULE, connect, [Listen]),
loop(Socket).
loop(Socket) ->
receive
{tcp, Socket, Data} ->
io:format("1st version received ~p~n", [Data]),
loop(Socket);
{tcp_closed, Socket} ->
io:format("socket closed~n")
end.
client.erl
-module(client).
-export([request/0]).
request() ->
{ok, Socket} = gen_tcp:connect("localhost", 8080, [{packet, raw}, binary]),
gen_tcp:send(Socket, <<"Hello">>).
Starting the server and sending a request creates the expected output.
1> server:start().
<0.62.0>
2> client:request().
ok
1st version received <<"Hello">>
After changing the format statement to "2nd version", compiling and loading the code and executing two requests (because the connect/1 process currently waiting for connections was spawned before the change) the result is still as expected.
3> c(server).
{ok,server}
4> client:request().
ok
1st version received <<"Hello">>
5> client:request().
ok
2nd version received <<"Hello">>
However, after compiling and loading the code twice in a row, there is no output printed in the terminal any more, although the server is obviously still running, since gen_tcp:connect returns a socket.
6> c(server).
{ok,server}
7> c(server).
{ok,server}
8> client:request().
ok
I suspect that this behaviour has something to do with erlang killing all processes with code older than two versions but I fail to really understand what is happening here.
Since this is educational I am more interested in knowing why this exact code doesn't work rather than an actual solution to the problem.
Thanks
I am pretty certain this is the two-versions limit.
If you want to confirm that, replace the calls from spawn(...) to spawn_link(...) -- if the processes die, your shell will crash as well and you'll know they have been killed.
Another way to test it is whether you can replace the following:
6> c(server).
{ok,server}
7> c(server).
{ok,server}
8> client:request().
ok
By:
6> c(server).
{ok,server}
7> client:request().
ok
8> c(server).
{ok,server}
9> client:request().
ok
If this works fine, the difference is the message in the middle that allows the code to update to a newer code version of the fully-qualified function call (Module:Fun(Args)), preventing the crash.

Running statements in shell gives different output

Simple code:
-module(on_exit).
-export([on_exit/2, test/0]).
on_exit(Pid, Fun) ->
spawn(fun() ->
Ref = erlang:monitor(process, Pid),
receive
{'DOWN', Ref, process, Pid, Why} ->
Fun(Why)
end
end).
test() ->
Fun1 = fun() -> receive Msg -> list_to_atom(Msg) end end,
Pid1 = spawn(Fun1),
Fun2 = fun(Why) -> io:format("~w died with error: ~w~n", [Pid1, Why]) end,
_Pid2 = spawn(on_exit, on_exit, [Pid1, Fun2]),
Pid1 ! hello.
In the shell:
1> c(on_exit).
{ok,on_exit}
2> on_exit:test().
<0.39.0> died with error: noproc
hello
3>
=ERROR REPORT==== 9-Apr-2017::05:16:54 ===
Error in process <0.39.0> with exit value: {badarg,[{erlang,list_to_atom,[hello],[]},{on_exit,'-test/0-fun-0-',0,[{file,"on_exit.erl"},{line,14}]}]}
Expected Output:
5> Pid1 ! hello.
<0.35.0> died with error: {badarg,[{erlang,list_to_atom,[hello],[]}]}
hello
6>
=ERROR REPORT==== 9-Apr-2017::05:15:47 ===
Error in process <0.35.0> with exit value: {badarg,[{erlang,list_to_atom,[hello],[]}]}
In fact, the expected output is what I see if I take each line in test() and paste it into the shell. Why do I get the noproc (no process) error when I run the same lines inside a function?
From the docs:
12.8 Monitors
An alternative to links are monitors. A process Pid1 can create a
monitor for Pid2 by calling the BIF erlang:monitor(process, Pid2). The
function returns a reference Ref.
If Pid2 terminates with exit reason Reason, a 'DOWN' message is sent
to Pid1:
{'DOWN', Ref, process, Pid2, Reason}
If Pid2 does not exist, the 'DOWN' message is sent immediately with
Reason set to noproc.
Your code contains a race condition -- spawn is asynchronous and might return before the process is spawned, and you might end up sending and crashing Pid1 before on_exit:on_exit/2 starts monitoring it, which causes the erlang:monitor/2 call to immediately send a noproc message to the caller:
1> Pid = spawn(fun() -> ok end).
<0.59.0>
2> erlang:monitor(process, Pid).
#Ref<0.0.1.106>
3> flush().
Shell got {'DOWN',#Ref<0.0.1.106>,process,<0.59.0>,noproc}
ok
The code works fine in the shell probably because the Erlang VM executes some things slowly in the shell than when the code is compiled, but this behavior is not guaranteed. This is a classic race condition.
Erlang has a solution for this: erlang:spawn_monitor/{1,3}. This function is guaranteed to attach the monitor as soon as the function is spawned. You'll have to re-arrange your code a bit to use it instead of spawn/3 + erlang:monitor/1.

Why doesn't spawn link cause the calling process to die?

From the example given here, Erlang and process_flag(trap_exit, true)
-module(play).
-compile(export_all).
start() ->
process_flag(trap_exit, true),
spawn_link(?MODULE, inverse, [***0***]),
loop().
loop() ->
receive
Msg -> io:format("~p~n", [Msg])
end,
loop().
inverse(N) -> 1/N.
If I run it as,
A = spawn(play, start, []).
The spawned process <0.40.0> dies as it is suppose to but the main process (A <0.39.0>) which spawned it doesn't die.
{'EXIT',<0.40.0>,{badarith,[{play,inverse,1,[{file,"play.erl"},{line,15}]}]}}
<0.39.0>
i().
....
....
<0.39.0> play:start/0 233 19 0
play:loop/0 1
A does get an exit signal (not an exit message since A is not trapping exit) then why doesn't it exit?
The reason for this is that you set a trap_exit flag to true which means this process will get {'EXIT', FromPid, Reason} message instead of being killed. Just remove process_flag(trap_exit, true) or in case of receiving this type of message, kill it.
You can read about it here.

Avoiding a race condition in erlang

Is this how you avoid a race condition?
-module(b).
-export([my_spawn/1]).
my_spawn(Func) ->
Pid = spawn(listener()),
Pid ! {self(), spawn, Func},
receive
{From, Desired_Pid} -> Desired_Pid
end.
listener() ->
receive
{From, spawn, Func} ->
{Pid,Ref} = spawn_monitor(Func),
From ! {self(), Pid},
receive
{'DOWN',Ref, process, _, _} -> io:format("I lived for [calculated how long i lived]")
end
end.
What I'm trying to achieve here is
A = spawn(proc),
monitor(process,A).
However A might die before the second line executes.
If you spawn a process and it dies just before you create your monitor, you will still receive the DOWN message:
1> Pid = spawn(erlang,now,[]).
<0.35.0>
2> is_process_alive(Pid).
false
3> monitor(process, Pid).
#Ref<0.0.0.86>
4> flush().
Shell got {'DOWN',#Ref<0.0.0.86>,process,<0.35.0>,noproc}
As this shell session shows, we first spawn an intentionally short-lived process, and we use is_process_alive to verify that it's dead. We then create a monitor for the process, and then we flush the shell's message queue and see that it did indeed receive a DOWN message for the already-deceased process.

Erlang pids from console

I saw message:
https://stackoverflow.com/a/4837832/1236509
with supervisor:
-module(root_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
{ok, Pid} = supervisor:start_link({local, ?MODULE},
?MODULE, []),
erlang:unlink(Pid),
{ok, Pid}.
init(_Args) ->
RestartStrategy = {simple_one_for_one, 10, 60},
ChildSpec = {ch1, {ch1, start_link, []},
permanent, brutal_kill, worker, [ch1]},
Children = [ChildSpec],
{ok, {RestartStrategy, Children}}.
In console man calls:
{ok, ChildPid1} = root_sup:start_link().
when child pid changes how would ChildPid1 get new pid so always can use ChildPid1 with correct pid? Need way to link to part of supervisor creating child.
I would not try to access the child by Pid but instead register/2 the child process under a name, so it's accessible regardless of the actual Pid.
Using the code from the answer you reference, a simple way of doing this is to add register(ch1, self()), to the init procedure of the child. This would give, for ch1.erl:
init(_Args) ->
io:format("ch1 has started (~w)~n", [self()]),
% register a name to this process
register(child, self()),
{ok, ch1State}.
This registers the pid of the child self() to the name child
We can see it works:
1> root_sup:start_link().
{ok,<0.34.0>}
2> supervisor:start_child(root_sup, []).
ch1 has started (<0.36.0>)
{ok,<0.36.0>}
3> lists:filter(fun(X) -> X == child end, registered()).
[child]
we indeed have a process registered under the name of child.
4> gen_server:cast(child, calc).
result 2+2=4
and it is a correct process running the code from ch1.erl.
Let us crash this process, by invoking the bad code:
5> gen_server:cast(child, calcbad).
result 1/0
ok
ch1 has started (<0.41.0>)
6>
=ERROR REPORT==== 28-Oct-2012::01:31:30 ===
** Generic server <0.36.0> terminating
** Last message in was {'$gen_cast',calcbad}
** When Server state == ch1State
** Reason for termination ==
** {'function not exported',
[{ch1,terminate,
[{badarith,
[{ch1,handle_cast,2,[{file,"ch1.erl"},{line,27}]},
{gen_server,handle_msg,5,
[{file,"gen_server.erl"},{line,607}]},
{proc_lib,init_p_do_apply,3,
[{file,"proc_lib.erl"},{line,227}]}]},
ch1State],
[]},
{gen_server,terminate,6,[{file,"gen_server.erl"},{line,722}]},
{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,227}]}]}
So the child, the process <0.36.0> crashed, and a new child <0.41.0> was started to assume the duties of the deceased <0.36.0>. Since this new process is registered under the same name, calc will work again:
6> gen_server:cast(child, calc).
result 2+2=4
ok
Note that this does not guarantee that the gen_server:cast/2s always result in the execution of the corresponding code because the child might have been killed just now and the new process still was not started (registered in fact).
You might want to refer to the excellent Programming Erlang: Software for a Concurrent World by Joe Armstrong for more details about process registration, supervisors, OTP and more. Many details can also be found in the online documentation of OTP.

Resources